目次

modbusのLRC計算(Python)

 PLC(Programmable Logic Circuit)の通信で利用される
 modbusに関する誤り検出に使う符号を求めるスクリプト
 を作成してみました。

 modbusのフレームにASCIIに関する規定は、以下。



 具体的には。次のように1フレームが構成されます。

    ':' '01' '06' 'F9' '\r' '\n'

 よく見ると、先頭とCR、LFを除いた2バイトが
 必要なデータとわかります。

 modbusでフレームをASCIIで表現すると、以下。

 「:」で始まり、CR、LFで1レコードを終えます。
 「:」とCRで挟んだ16進数で、送信するデータを
 表現。CRのすぐ前の16進数2文字が、LRCです。

 フレームを生成するPythonの関数を定義してみました。

def makeSData(x):
  # convert list
  xx = x
  if isinstance(x,int) :
    xx = [x]
  # initialize
  result = []
  # header
  result.append( ':' )
  # make ASCII and calculate sum
  sum = 0
  for e in xx :
    sum += e
    result.append( getAscii2(e) )
  # generate LRC
  lrc = calc2compliment(sum)
  # add LRC
  result.append( getAscii2( lrc ) )
  # add CR/LF
  result.append( '\r' )
  result.append( '\n' )
  #
  return result

 この関数を利用すると、パラメータを与えることで
 1フレームを生成します。



 関数に、数値か数値を要素とするリストを渡すと
 1フレーム分のリストを生成しています。

 リストか数値だけかは、組込み関数isinstanceを
 使って判定。

 数値だけがパラメータで与えられたなら、リストにし
 後の処理が単純になるようにしました。

 LRCは、次のアルゴリズムで求めます。

 Address、Function、Dataをそれぞれ8ビットの16進数
 数値とみなして、加算。加算結果の2の補数を求めて
 下位8ビットをLRCとする。

 関数makeSDataのパラメータは、数値かリストで
 与えられ、内部でリストになっているのでリスト
 の要素の数値を2文字の数字に変換します。

 この変換は、次の関数で実現。

def getAsc(x):
  mycode = '0123456789ABCDEF'
  if x > 15 :
    result = '0'
  else :
    result = mycode[x]
  #
  return result

def getAscii2(x):
  # separate
  q = (x >> 4) & 15
  r = x & 15
  # concatenate
  result = getAsc(q)
  result += getAsc(r)
  #
  return result

 関数getAscをプリミティブにし、関数getAscii2を
 ラッパーにして対応しました。

 LRCを計算するので、3要素(Address Function Data)を
 すべて加算しています。

 加算結果から、LRCを算出するために2の補数を
 計算する関数を定義。

def calc2compliment(x):
  # 1's complement
  result = x ^ 0xffff
  # 2's complement
  result += 1
  # get 8 bits
  result &= 0xff
  #
  return result

 LRCが求められたなら、Dataの後において、CR、LFを
 追加します。

 与えられたフレームの中にある3要素(Address Function Data)が
 正しく伝送されてきたのかを判定するためにFRCを使います。
 その判定のための関数を用意します。

 フレームの中の3要素(Address Function Data)から
 仮のLRCを算出し、フレームに埋め込まれているLRCと
 比較して、一致するか異なるかを判定すればよいはず。

 判定に使う関数は、次のように定義。

 フレームの中の3要素(Address Function Data)から
 仮のLRCを算出し、フレームに埋め込まれているLRCと
 比較して、一致するか異なるかを判定すればよいはず。

 3要素から仮のLRCを計算することとフレームに
 埋められているLRCを取り出すのは、簡単。
 一致すればTrueを返し、異なればFalseを返せば
 よいと考えました。

def is_lrc_ok(x) :
  # get LRC
  xlen = len(x)
  lrc  = getHex2( x[xlen-3] )
  # sum
  sum = 0
  for e in x[1:xlen-3] :
    sum += getHex2( e )
  # generate lrc
  mylrc = calc2compliment(sum)
  # default
  result = False
  if lrc == mylrc :
    result = True 
  #
  return result

 CRの前に、LRCが要素として配置されているので
 フレーム内のLRCを取り出しておきます。

 スライスを使い、リストの中から3要素を取得して
 加算して合計を求め、2の補数に変換。

 2つのLRCが一致したなら、Trueとしています。

 関数is_lrc_okの中で、数値の加算が必要になるため
 数字から数値へ変換する関数を用意。

def getHex(x) :
  # default
  result = 0
  xx = ord(x)
  if ord('0') <= xx and xx <= ord('9') :
    result = ord(x) - ord('0')
  if ord('A') <= xx and xx <= ord('F') :
    result = ord(x) - ord('A') + 10
  if ord('a') <= xx and xx <= ord('f') :
    result = ord(x) - ord('a') + 10
  #
  return result

def getHex2(x) :
  # calculate
  result = getHex( x[0] ) 
  result *= 16
  result += getHex( x[1] )
  #
  return result

 数字1文字(0から9、AからF)を数値に変換する
 関数getHexを定義し、そのラッパーに関数getHex2を
 用意して、わかりやすくしておきます。

 次のスクリプトで、動作をテストしました。

xx = [1,2]
yy = makeSData( xx )
print( xx , makeSData( xx ) )
print( is_lrc_ok( yy ) )

xx = [15,14]
yy = makeSData( xx )
print( xx , makeSData( xx ) )
print( is_lrc_ok( yy ) )

yy = makeSData( [1,6] )
print( yy , is_lrc_ok( yy ) )

 結果は、次のようになります。



 これで、定義した関数が問題ないことがわかり
 アルゴリズムとしてのシーケンスが妥当だと
 理解できました。


目次

inserted by FC2 system