Modbusの利用 (6) 電力計SDM120M

 EASTRONの電力計SDM120Mは、Modbus/RTUのインターフェースをもっています。スリムな形状をしているので、DINレールに取り付けてもスペースを食いません。Windows10のPython3から利用します。実行環境などは、前回の記事を参照してください。

SDM120Mのおもなスペック

  • 単相230V(176~276V AC)※100Vでも動作した
  • 電圧/電流確度 0.5%
  • 測定電流範囲 0.25~45A
  • 動作温度 -25~55℃
  • インターフェース Modbus/RTU、デフォルト・アドレス=1、RS-485通信デフォルト値、2400bps、8ビット、パリティなし、ストップ・ビット1

Modbus

 Input Registerから下記のパラメータを読み取ります。

Address Description Units
30001 Voltage Volts
30007 Current Amps
30013 Active Power Watts
30019 Apparent Power VoltAmps
30025 Reactive Power VAr
30031 Power Factor None
30037 Phase Angle Degrees
30071 Frequency Hz
30073 Import Active Energy kWh
30075 Export Active Energy kWh
30077 Import Reactive Energy kVArh
30079 Export Reactive Energy kVArh
30343 Total Active Energy kWh
30345 Total Reactive Energy kVArh

接続

 終端抵抗は入れずに実験しています。

プログラム

 Input Registerから読み取ったデータは、16ビット・データが2ワードで、フォーマットはIEEE754(単精度浮動小数点数の形式: binary32)です。4バイトの16進データを読み取った後、10進数へ変換します。Pythonはいろいろなライブラリがあるのでと思い探しましたが、見つかりませんでした。

 データシートにも、次に示すサンプル・データを10進数へ変換する手順が掲載されています。ウィキペディアの「単精度浮動小数点数」の「単精度バイナリ形式から十進への変換」の項目にも手順が書かれていますが、少し手法が異なります。ここでは、データシートの手法に従います。

上位レジスタの上位バイト 上位レジスタの下位バイト 下位レジスタの上位バイト 下位レジスタの下位バイト
0x43 0x70 0x80 0x00
0b0100011 0b01110000 0b10000000 0b00000000

A = master.execute(Address, cst.READ_INPUT_REGISTERS, 0, 2)

 この処理は、2バイトを読み出すと思っていたのですが、実際は、2バイトを二つ読み出しました。したがって、print(A[0],A[1])は、0x4370、0x8000になります。扱いやすいように、上位ワードを16ビット・シフトし、下位ワードと加算して一つにまとめます。

A0 = A[0]*65536+A[1]
print(bin(A0))


#0b1000011011100001000000000000000

ですが、最上位の0が消えているので実際は、0b01000011011100001000000000000000です。

 IEEE754(単精度浮動小数点数の形式)のデータは下記の順序になっています。最初の1ビットは符号です。次の8ビットはexponent=指数です。

  • 符号 0
  • 指数部 10000110
  • 仮数部 11100001000000000000000

 ここで、指数部の2進数10000110を10進に直すと134です。理由は置いておいて127を引くと7です。7を本来の指数と呼ぶようです。正の数だと次のプログラムで取り出せます。この測定器では負の数は出てこないので、符号が負のときの判別はしていません。

fp = int(str(bin(A0)[2:10] ),2) - 127
print('exponet is ', fp)


#exponet is 7

 仮数部は23ビットあります。暗黙の了解で、先頭の1.0が省略されています。したがって、

仮数部 1.11100001000000000000000

と正規化されています。正規化されているというのは、指数が1になったときです。

  仮数部 × 2^指数部

 ここで指数部は7ですから、ゼロ(.)を右に7、ずらします。

11110000.1000000000000000

 小数点の左が整数、右が小数の位になります。

newFr = '1'+str(bin(A0)[10:])
print('1fraction is ', str(newFr))
print('2fraction integer is ', newFr[:fp+1])
print('3fraction decimal is ', newFr[fp+1:])


#1fraction is 111100001000000000000000
#2fraction integer is 11110000
#3fraction decimal is 1000000000000000

 整数(integer)は、次のように10進に直せます。

  (1 × 2^7) + (1 × 2^6) + (1 × 2^5) + (1 × 2^4) + (0 × 2^3)+ (0 × 2^2) + (0 × 2^1)+ (0 × 2^0)  =  240

 小数(decimal)は次のように10進に直せます。

   (1 × 2^-1) + (0 × 2^-2)+ (0 × 2^-3) + … = 0.5

 合成すると240.5になります。

 FdecimalisとFintergerisは内包形式で1行で記述しています。Fdecimalisはコメントのところは、ループ形式で記述したプログラムです。

integeris = newFr[:fp+1]
decimalis = newFr[fp+1:]
Fdecimalis = sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(decimalis)])
print(Fdecimalis)
'''
data="11110000"#sample
sum0 =Decimal(0)
for (i,x) in enumerate(data) :
        sum0 += Decimal(x)*Decimal(2**((fp-i)))
        print(Decimal(x),i,sum0)
'''
Fintergeris = sum([(float(Decimal(x)*Decimal(2**(fp-i)))) for (i,x) in enumerate(integeris)])
print(Fintergeris)
print(Fintergeris+Fdecimalis)

 全体のプログラムです。上記のように、途中経過の計算例も入っているので長いです。

import sys
sys.path.append('C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\Python\\Python38\\Lib\\site-packages')
import serial
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu
from decimal import *

PORT = 'COM8'
Address = 1
master = modbus_rtu.RtuMaster(serial.Serial(port=PORT, baudrate=2400, bytesize=8, parity='N', stopbits=1, xonxoff=0))
master.set_timeout(1.0)
master.set_verbose(True)
#print("connected")
A = master.execute(Address, cst.READ_INPUT_REGISTERS, 70, 2)

#A=(0x4370,0x8000)

print(A[0],A[1])
print(bin(A[0]),bin(A[1]))
print(bin(A[0]*65536+A[1]))
print(hex(A[0]*65536+A[1]))
A0 = A[0]*65536+A[1]
print(bin(A0))

fp = int(str(bin(A0)[2:10] ),2) - 127
print('exponet is ', fp)

newFr = '1'+str(bin(A0)[10:])
print('1fraction is ', str(newFr))
print('2fraction integer is ', newFr[:fp+1])
print('3fraction decimal is ', newFr[fp+1:])

integeris = newFr[:fp+1]
decimalis = newFr[fp+1:]
Fdecimalis = sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(decimalis)])
print(Fdecimalis)
'''
data="11110000"#sample
sum0 =Decimal(0)
for (i,x) in enumerate(data) :
        sum0 += Decimal(x)*Decimal(2**((fp-i)))
        print(Decimal(x),i,sum0)
'''
Fintergeris = sum([(float(Decimal(x)*Decimal(2**(fp-i)))) for (i,x) in enumerate(integeris)])
print(Fintergeris)
print(Fintergeris+Fdecimalis) 

実行例

 電圧は、

A = master.execute(Address, cst.READ_INPUT_REGISTERS, 0, 2)

 周波数は、

A = master.execute(Address, cst.READ_INPUT_REGISTERS, 70, 2)

(2020/04/11)Structを使うともっと簡単に記述できるかもしれない。

参考;IEEE単精度実数形式から10進数への変換

Pythonで浮動小数点数floatと16進数表現の文字列を相互に変換

【C&Python】浮動小数点数のバイト列16進法表記

Floating Point to Hex Converter