前回に引き続き、直流の電流・電圧を測る回路で測定値を読んでみることにします。計測する本体には Raspberry Pi と BeagleBone Green を使ってみました。センサとの I/F は I2C です。今回は Nerves / Elixir を使用して実際にラズパイの消費電力を測定してみます。
今回で自動計測まで全部書ききるつもりだったのですが iex(Elixir の REPL)を使って遊んで見るところまでで力尽きました。GenServer を使った自動計測はまた次にします。
なお、今回は Nerves を使っていますが、Circuits モジュールは Nerves じゃないとう動かないということはないので、Linux 上の普通の Elixir でも同様のことができます。私自身は試していませんが BeagleBone 用の Linux や Raspbian 上の Elixir でも動くと思います。
準備
ということで、例によって Nerves の準備をします。以下は慣れた方用のメモ程度です。細かい話は「リンク集」 などを追いかけて下さい。
$ mix nerves.new ina226
$ cd ina226
$ export MIX_TARGET=bbb # これは BeagleBone の場合。ラズパイの場合は rpi0 rpi4 など
mix.exs
に Circuits.I2C を使うための依存関係を記述します。
$ diff -c mix.exs{,.org}
***************
*** 49,57 ****
{:nerves_system_rpi4, "~> 1.13", runtime: false, targets: :rpi4},
{:nerves_system_bbb, "~> 2.8", runtime: false, targets: :bbb},
{:nerves_system_osd32mp1, "~> 0.4", runtime: false, targets: :osd32mp1},
! {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64},
! {:circuits_gpio, "~> 0.4"},
! {:circuits_i2c, "~> 0.1"},
]
end
--- 49,55 ----
{:nerves_system_rpi4, "~> 1.13", runtime: false, targets: :rpi4},
{:nerves_system_bbb, "~> 2.8", runtime: false, targets: :bbb},
{:nerves_system_osd32mp1, "~> 0.4", runtime: false, targets: :osd32mp1},
! {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64}
]
end
あと config/target.exs
にご自身のネットワーク環境を入れて下さい。BeagleBone で virtual Ethernet を使うときは特に変更の必要はないです。
$ mix deps.get
$ mix firmware
$ mix burn
で SD カードにファームウェアを焼いてください。それをコンピューティングボードに挿して(bbb なら User ボタンを押しながら)電源ONです。
$ ping nerves.local
$ ssh nerves.local
で iex のプロンプトが出るのを確認して下さい。
回路を作る
ここでは 前回説明した絶縁型回路 で試します。
上が被計測用の Raspberry Pi 4 で、下が計測用のBeagleBone Green です。間にあるのが Strawberry Linux 製の絶縁型計測基板 INA226PRCiso です。オレンジの破線で計測側と被計測側が電気的に絶縁されてます。
この計測ボードの I2Cバスのスレーブアドレスは 0x41 == 65 になるようにしてます。
動作確認
まずは I2C インタフェースを認識しているか確認しましょう。
以下は BeagleBone 用で I2C のバスは "i2c-2"
を使います。ラズパイを計測用に使う場合は "i2c-1"
を使って下さい。
iex(1)> Circuits.I2C.detect_devices("i2c-2") # BeagleBone の場合
'A'
返ってきたリストの中身がたまたま印字可能なビット表現なので、文字リストで表示されてしまいます。数値で見るためにリストの先頭を取り出してみます。
iex(2)> Circuits.I2C.detect_devices("i2c-2") |> hd
65
と基板のランドをハンダでショートしておいたとおり INA226 のスレーブアドレスが 65 になってるのがわかります。以下ではスレーブアドレスとして 65 を決め打ちします。
手動で計測値を見る
では早速 I2C をオープンしてみます。
iex(1)> {:ok, ref} = Circuits.I2C.open("i2c-2") # I2C をオープンする
{:ok, #Reference<0.3777474050.537001992.12800>}
:ok
と I2C バスインタフェース用のリファレンスが返ってきました。以降は I2C デバイスにアクセスするのにこの ref
を参照するようにします。
動作モード(レジスタ0)
レジスタ0は INA226 の動作を決定します。電源オン直後はデフォルト値の 0x4127 が返ってくるはずです。
参考:レジスタ0に関する前回記事
iex(2)> Circuits.I2C.read(ref, 65, 2)
{:ok, "A'"}
と、なにかが返ってきてるのがわかります。
タプルの第2要素がこのままだと読みにくいので、わかりやすくなるようにパターンマッチさせてみます。Elixir はこれが大変得意で、簡単にビットフィールドを切り出して特定の型にマッチしてくれます。
では、返ってくる値をバイナリとしてパターンマッチさせてみましょう。
iex(3)> {:ok, <<val::unsigned-integer-size(16)>>} = Circuits.I2C.read(ref, 65, 2)
{:ok, "A'"}
iex(4)> inspect(val, base: :hex)
"0x4127"
一旦 val
を 16bit の符号なし整数にマッチさせて、それを 16進数で表示させてます。予定通り 0x4127 でした。
レジスタを切り替えて値を読む
上の Circuits.I2C.read(ref, 65, 2)
は電源起動直後ならOKです。もしほかのレジスタを読みに行った後でしたら、読み出すレジスタを 0 に切り替える必要があります。その場合は以下のようにしてください。
iex(2)> Circuits.I2C.write_read(ref, 65, <<0>>, 2)
{:ok, "A'"}
これはレジスタ0に切り替えるべく <<0>>
をI2Cに書いた直後に 2バイトを読み込む関数です。これは電源投入直後にやっても問題ないです。また一度、レジスタを設定したら次にレジスタを設定するまで、読み出しは同一レジスタから行われます。
出荷時データを読んでみる(レジスタ254〜255)
いきなり寄り道します。前回記事のレジスタ254〜レジスタ255にしたがって、メーカ番号、デバイス番号、リビジョン番号を見てみましょう。
メーカ番号
マニュアルによるとレジスタ254には 0x5449 が入っているはずです。レジスタ254をアクセスできるようにして、2バイトを読み出してみましょう。
iex(5)>Circuits.I2C.write_read(ref, 65, <<254>>, 2)
{:ok, "TI"}
はい、メーカの略称が出てきました。なるほど、やるなぁ。
デバイス番号、リビジョン番号
今度はデバイス番号とリビジョン番号を読んでみます。
iex(11)> Circuits.I2C.write_read(ref, 65, <<255>>, 2)
{:ok, "\"`"}
ん〜、これでは何が返ってきてるのかよくわかりませんね。マニュアルを読むと4bitずつに区切られているようですからそのように読んでみましょう。
iex(12)> {:ok, <<a::size(4), b::size(4), c::size(4), r::size(4)>>} = Circuits.I2C.write_read(ref, 65, <<255>>, 2)
{:ok, "\"`"}
iex(13)> [a, b, c, r]
[2, 2, 6, 0]
先頭12bitでパーツ番号の 226 を、後ろの4bitでリビジョン番号 0 を表現しているのがわかりました。
レジスタ1で電流を読んでみる
では 前回記事のレジスタ1 に従って電流を読んでみましょう。
電流計測用のレジスタ1の値は16bitの2の補数表現ですので、読み出すときにそのようにバイナリをパターンマッチさせてみます。
iex(29)> {:ok, <<val::signed-integer-size(16)>>} = Circuits.I2C.write_read(ref, 65, <<1>>, 2)
{:ok, <<14, 190>>}
iex(34)> val
3774
シャント抵抗が 20mΩのとき LSB 1bit が 0.1mA に相当しますので 377.4 mA であることがわかります。
iex(35)> {:ok, <<val::signed-integer-size(16)>>} = Circuits.I2C.read(ref, 65, 2)
{:ok, <<14, 192>>}
iex(36)> val/10
377.6
iex(37)> {:ok, <<val::signed-integer-size(16)>>} = Circuits.I2C.read(ref, 65, 2)
{:ok, <<14, 187>>}
iex(38)> val/10
377.1
何度もやるとちょっとずつ電流値が変わっているのがわかります。
レジスタ2で電圧値を読む
それでは 前回記事のレジスタ2 に従って電圧を読み出してみます。今度は16bitの正の整数ですのでそのようにマッチさせてみます。
iex(40)> {:ok, <<val::unsigned-integer-size(16)>>} = Circuits.I2C.write_read(ref, 65, <<2>>, 2)
{:ok, <<15, 175>>}
iex(41)> val
4015
LSB 1bit が 0.00125V (==1.25mV ==1/800) です。
iex(42)> val / 800.0
5.01875
となって 5V よりちょっと高い電圧が出てるのがわかります。
iex(44)> {:ok, <<val::unsigned-integer-size(16)>>} = Circuits.I2C.read(ref, 65, 2)
{:ok, <<15, 174>>}
iex(45)> val/800
5.0175
iex(46)> {:ok, <<val::unsigned-integer-size(16)>>} = Circuits.I2C.read(ref, 65, 2)
{:ok, <<15, 175>>}
iex(47)> val/800
5.01875
何度もやると電圧もちょっとずつ変化してるのがわかります。
レジスタ5で結果の値をいい塩梅にする
前回記事のレジスタ4〜レジスタ5で電流値の補正計算機能を使ってみましょう。
ここでは元の意味での補正とは異なる使い方をしてみます。補正とは別途正確な計測器を用意してこの装置の計測値が正しい値を示すようにする作業を指します。以下でやるのはレジスタ3やレジスタ4に「人間が分かりやすい値が直接入る」ようにするための技です。
レジスタ4の電流値を分かりやすくする
では、まずいきなりレジスタ4を読んでみます。
iex(53)> {:ok, <<val::unsigned-integer-size(16)>>} = Circuits.I2C.write_read(ref, 65, <<4>>, 2)
{:ok, <<0, 0>>}
すると 0 が返ってきます。これはレジスタ5のデフォルト値が 0 なためです1。
今度は、レジスタ5に値を入れてみましょう。2048を入れると倍率が 1.0 倍になります。ここでは 0.1 倍にしてみたいので 2048/10 に最も近い整数 round(2048/10)
(==205) をセットします。
iex(62)> Circuits.I2C.write(ref, 65, <<5, 0, round(2048/10)>>)
:ok
iex(63)> {:ok, <<val::unsigned-integer-size(16)>>} = Circuits.I2C.write_read(ref, 65, <<4>>, 2)
{:ok, <<1, 124>>}
iex(64)> val
380
と、380mA という数値が直接レジスタ4から読み出せました。
レジスタ3から電力を分かりやすい値で読んでみる
今度は電力の値を見てみましょう。ここでも INA226PRC / INA226PRCiso 基板同様にシャント抵抗が 20mΩ の場合で考えます。
参考:前回記事のレジスタ3
- シャント抵抗が 20mΩ の場合のレジスタ1の LSB 1bit は 0.1mA でした。
- シャント抵抗に関わらず、レジスタ2の LSB 1bit は 1.25mV です。
- 補正係数が 1.0(レジスタ5が2048)とすると、レジスタ4はレジスタ1と同じ値を持ちます。よって LSB 1bit が 0.1mA に相当します。
- レジスタ3は、レジスタ2とレジスタ3との積の20000分の1です。
よってレジスタ3のLSB 1bit が表現する電力は $0.1mA\times 1.25mV\times 20000 = 2.5mW$ になります。この場合では、例えば消費電力が 400mW の場合には、レジスタ3の値は 160 (400/2.5) として読み出されることになります。
次にレジスタ3の LSB 1bit が表現する電力を 1mW にしてみたくなったとします。補正係数と1bitの表現する値は反比例しますので、補正係数を 2.5 にすると良いことになります。したがって以下のようになります。
- レジスタ1の LSB 1bit は 0.1mA
- レジスタ2の LSB 1bit は 1.25mV
- 補正係数が2.5(レジスタ5が5120)とすると、レジスタ4はレジスタ1の2/5倍の値になるので LSB 1bit で 0.04mA に相当
- レジスタ3は、レジスタ2とレジスタ3との積の20000分の1…
よってレジスタ3のLSB 1bit が表現する電力は $0.04mA\times 1.25mV\times 20000 = 1.0mW$ になります。この場合では、例えば消費電力が 400mW の場合には、レジスタ3の値も 400 として読み出されることになります。
どうなるか実際に試してみましょう。
iex(111)> Circuits.I2C.write(ref, 65, <<5, 0x14, 0 >>) # 補正係数を 2.5 (= 5120/2048) に設定する
:ok
iex(112)> {:ok, <<curr::signed-integer-size(16)>>} = Circuits.I2C.write_read(ref, 65, <<1>>, 2) # 電流を読んで curr に入れる
{:ok, <<14, 212>>}
iex(114)> {:ok, <<volt::unsigned-integer-size(16)>>} = Circuits.I2C.write_read(ref, 65, <<2>>, 2) # 電圧を呼んで volt に入れる
{:ok, <<15, 173>>}
iex(115)> {:ok, <<watt::unsigned-integer-size(16)>>} = Circuits.I2C.write_read(ref, 65, <<3>>, 2) # 電力を呼んで watt に入れる
{:ok, "\au"}
電力は電流×電圧で計算できますので iex 上で計算してみます。
iex(119)> curr * 0.1
379.6
iex(120)> volt * 0.00125
5.01625
iex(121)> curr * 0.1 * volt * 0.00125
1904.1685
電流が 379.6mA、電圧が 5.01625V、電力が約 1904mW と求まりました。
一方でチップのレジスタ3から直接読みだした値を見てみます。すると…
iex(122)> watt
1909
1909mW となって先程の値と近い値になってます。値が僅かにことなるのは、レジスタ1を読んだタイミングと、レジスタ3を読んだタイミングが若干ずれているからです。
このように補正係数をうまいこと設定してやると、プログラム側で計算しなくてもレジスタより直接電力の値を読み出すことができます。
Elixirでやると「なんでわざわざこんなことを…」と思いますが、組込み用の非力なCPUで電流・電圧・電力を求めるにはこのようにしてハードウェアに任せる方法もあるでしょう。あるいは、電流・電圧の値は不要でもっぱら電力値が必要な場合はこうしておいてレジスタ3しか読まないという手もあります。
まとめ
今回は TI INA226 を用いた電流・電圧・電力計測をやってみました。電力の計算はソフトウェアで行う方法とハードウェアにやらせる方法がありました。
なお、今回はボードには INA226PRCiso を用いていますが、INA226 であれば同様に動かすことができます。
次回は Nerves がオンになったら自動で電力を計測するようなプログラムを作ります。乞うご期待。
参考文献
- はじめてNerves(0) ElixirによるIoTフレームワークNervesがとにかく動くようになるためのリンク集
- 組込みに欠かせない Elixir でのビットの扱い方
- Elixir official document, Kernel.SpecialForms
- Elixir official document, Kernel.SpecialForms, ::/2
- Elixir official document, Kernel.SpecialForms, <<>>/1
- はじめてNerves(10) I2C デバイス INA226 で電流・電圧・電力を測ってみる (1/3)
- はじめてNerves(12) I2C デバイス INA226 で電流・電圧・電力を測ってみる (3/3)
- Texas Instruments INA226
- Strawberry Linux
- Strawberry Linux 製 INA226PRC
- Strawberry Linux 製 INA226PRCiso
-
このため、電源オンの直後はレジスタ3〜5が全部 0 になってしまいます。デフォルト値としてレジスタ5に 0 以外(例えば補正値が 1.0 になる 2048 とか)を入れててくれると、なにか計測したときにレジスタ1〜5に全部 0 以外の値が入って多少デバッグのときの安心感が違うのですがね。 ↩