INA226 を RasPi の ruby でi2c

PCB が組みあがったのですが、これを RasPi で i2c するサンプルをググってみたら、いくつかありました。

RasPi は、i2c が使えるようにモジュールをロードしておきました。RasPi でi2c は初めて使います。無事に出来るでしょうか?

# lsmod
Module                  Size  Used by
★i2c_dev                 6027  0 
cfg80211              386508  0 
rfkill                 16651  1 cfg80211
rpcsec_gss_krb5        20958  0 
nfsd                  263569  2 
snd_bcm2835            18649  0 
snd_pcm                73475  1 snd_bcm2835
snd_seq                53078  0 
snd_seq_device          5628  1 snd_seq
snd_timer              17784  2 snd_pcm,snd_seq
snd                    51038  5 snd_bcm2835,snd_timer,snd_pcm,snd_seq,snd_seq_device
★i2c_bcm2708             4990  0 
joydev                  8879  0 
spi_bcm2708             5137  0 
evdev                   9950  2 
uio_pdrv_genirq         2958  0 
uio                     8119  1 uio_pdrv_genirq

 

先輩たちがサンプルコードは書いているはずなので、探したところ、python でやるのとか、ruby でやるのとかがすぐ見つかりました。

とりあえず、動作確認したいので以下のサイトのコードを参考に必要な部分だけ使わせてもらいました。

Rabbit Note

BeagleBone Black で作るロギング機能付き電力計 (ソフト編)

BeagleBone Black(BBBと略するようです)での環境ですが、大変参考になりました。

使わせていただいたコードは最下部に付けておきます。で、配線は写真のようにちょっと雑ですが配線。負荷には、ちっさいファンをつけてみました。

SingleShot0023 スレーブアドレスは、何も設定しないと、0x40 が設定されるようです。

# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --  

 

プログラムを起動すると、以下のようになります。

# ./test.ruby
--------- 2016/01/25, 01:33:22
4.78125 V
151 mA
0.29 W
--------- 2016/01/25, 01:33:22
4.78125 V
151 mA
0.289 W
--------- 2016/01/25, 01:33:23
4.78875 V
152 mA
0.291 W
--------- 2016/01/25, 01:33:23
4.7875 V
151 mA
0.29 W
--------- 2016/01/25, 01:33:24
4.7875 V
150 mA
0.289 W
--------- 2016/01/25, 01:33:24
4.7875 V
151 mA
0.29 W
--------- 2016/01/25, 01:33:25
4.79 V
152 mA
0.292 W

負荷にLED を点けてみると、以下のようです。

# ./test.ruby 
--------- 2016/01/25, 01:35:31
4.88 V
2 mA
0.005 W
--------- 2016/01/25, 01:35:31
4.88 V
2 mA
0.005 W
--------- 2016/01/25, 01:35:31
4.9125 V
2 mA
0.005 W
--------- 2016/01/25, 01:35:32
4.91375 V
2 mA
0.005 W
--------- 2016/01/25, 01:35:32
4.91375 V
2 mA
0.005 W

 

まだあまりちゃんと理解していませんが、内部のレジスタの ox05 に、キャリブレーションする設定値を入れるようです。

以下の C のサンプルコードから、シャント抵抗に0.025R を使っている場合は、以下のように計算した16進数を入れればよいようです。書き込むとき、以下のコードでは、リトルエンディアンに変換していました。

https://github.com/jarzebski/Arduino-INA226/blob/master/INA226.cpp

0.025Ω = 0.00512/(0.025*0.0001)=2048 = 0x08 0x00

 

0x05 の補正レジスタを見てみると、

# i2cget -y 1 0x40 0x05 w

0x0008

という値が帰ってきました。

 

さて、そんな感じでとりあえずコードは以下のようにしてみました。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
 
IOCTL_I2C_SLAVE = 0x0703
 
# Script for power meter using the following parts
# - Power Meter module (IC: INA226)

# # i2cdetect -y 1
#      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
# 00:          -- -- -- -- -- -- -- -- -- -- -- -- --
# 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 70: -- -- -- -- -- -- -- --

class PowerSenseor
  def initialize(i2c_bus=1, dev_addr=0x40)
    @i2c = File.open(sprintf('/dev/i2c-%d', i2c_bus), 'rb+')
    @i2c.ioctl(IOCTL_I2C_SLAVE, dev_addr)

    @dev_addr = dev_addr
    @v_val = 0
    @c_val = 0
    @p_val = 0

    # shunt resistor = 0.002Ω = 0.00512/(0.002*0.001)=2560 = 0x0a 0x00
    #                  0.025Ω = 0.00512/(0.025*0.0001)=2048 = 0x08 0x00
    #   内部レジスタ[0x04] (Current) = 内部レジスタ[0x01]×内部レジスタ[0x05] / 2048
    exec_cmd("i2cset -y 1 0x#{dev_addr.to_s(16)} 0x05 0x08 0x00 i")
    # conversion time = 332us, number of average = 16
    exec_cmd("i2cset -y 1 0x#{dev_addr.to_s(16)} 0x00 0x04 0x97 i")
  end

  def sense
    # i2cset/i2cget は 10ms オーダーの時間を消費するのでここでは使用しない 
    @i2c.write(0x02)
    @v_val = conver_signed(@i2c.read(2))

    # 0x04 電流レジスタ
    @i2c.write(0x04)
    @c_val = conver_signed(@i2c.read(2))

    # 0x03 Powerレジスタ
    @i2c.write(0x03)
    @p_val = conver_signed(@i2c.read(2))
  end

  def conver_signed(bytes)
    # convert endian
    return bytes.unpack('n').pack('S').unpack('s')[0].abs
  end

  def get_voltage
    return calc_voltage(@v_val)
  end

  def get_current
    return calc_current(@c_val)
  end

  def get_power
    return calc_power(@p_val)
  end

  def exec_cmd(cmd)
    val=`#{cmd} 2> /dev/null`
    raise StandardError, "FAIL: #{cmd}" unless $?.success?
    return val
  end

  def calc_voltage(v_val)
    return v_val * 1.25 / 1000.0
  end

  # 0x04 電流レジスタ
  def calc_current(c_val)
    return c_val / 10
  end

  # 0x03 Powerレジスタ
  def calc_power(p_val)
    # return p_val * 0.025
    return p_val * 0.001
  end
end

require 'optparse'
params = ARGV.getopts('lq')

data_list = []

Signal.trap(:INT){
  if params['l'] then
    printf("time,voltage,current,power\n")
    data_list.each{|data|
      printf("%10d,%.3f,%.3f,%.3f\n", data[0], data[1], data[2], data[3])
    }
  end
  exit(0)
}

sensor = PowerSenseor.new
require "date"

start_time = Time.now
i = 0
while true
  sensor.sense
  v = sensor.get_voltage
  c = sensor.get_current
  p = sensor.get_power

  dt = DateTime.now
  print "--------- "
  print(dt.strftime("%Y/%m/%d, %H:%M:%S"), "\n")

  print v
  print " V\n"
  print c
  print " mA\n"
  print p
  print " W\n"

  if ((i & 0x7F) == 0) then

  else
    sleep 0.5
  end
  i = (i & 0xff) + 1
end

 

▼まとめ

・0x05 にシャント抵抗値を入れた補正値を書き込む

・書き込む最は、16進をリトルエンディアンにして書き込む

・0x04 はPowerレジスタで、0x04 は電流レジスタ。

・LSB ってなに?

・ちっこい今使っているファンは、150mA くらい使っていて0.3W ほど

・c のサンプルはすぐに見つからなかったけども、どうんな感じになるんだろう?

・自分で作ったハードが動いてうれしぃ!

・レジスタって、どういう回路なの? どういう仕組みで覚えているんだろう?