オムロン KM20-B40-FLK RS-485 通信 2020/10/04

オムロンの電力量センサ KM20-B40-FLK RS-485 通信です。
Modbus ではなく、CompoWay/F というオムロン独自の通信手順 + FINS-mini コマンドです。
現在 KM20-B40-FLK は廃番になっており、代替機種の KM-N1-FLK は Modbus RTU にも対応しているようです


 電力量ではなくて、簡単に交流電流値を計測(PLC に接続)するだけであれば、下記 URD 社製が良さそうです。
 ■平均値整流型 0-5V 出力(歪波形は誤差大)
  https://jp.misumi-ec.com/vona2/detail/221000835720/
 ■実効値整流型 4-20mA 出力(2線式)
  https://jp.misumi-ec.com/vona2/detail/221000835731/


{
  OMRON KM20-B40-FLK (RS485タイプ)

  CompoWay/F は、オムロンの汎用シリアル通信における統一通信手順。
  FINS (Factory Interface Network service) とは、
 オムロンの FA ネットワーク上のコントローラ間で、メッセージ通信を行うためのプロトコル。

}
unit uni232test;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Uni232C;

type
  TForm5 = class(TForm)
    Uni232C1: TUni232C;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form5: TForm5;


implementation

{$R *.dfm}

// 接続
procedure TForm5.Button1Click(Sender: TObject);
var
  ret : integer;
begin
  Uni232C1.Port := 3;
  Uni232C1.BaudRate := 19200;
  Uni232C1.ByteSize := Bit8;
  Uni232C1.ParityBits := ParityNone;
  Uni232C1.StopBits := Stopbit1;
  Uni232C1.FlowControls := CtrlNone;

  ret := Uni232c1.Open;
  if ret < 0 then
    ShowMessage('Cannot OPEN' + Uni232C1.Error2Str(ret));
end;

// 切断
procedure TForm5.Button2Click(Sender: TObject);
begin
  if Uni232C1.Connect then
    Uni232C1.Close;
end;

// データ読み込み
procedure TForm5.Button3Click(Sender: TObject);
var
  txData, rxData : TBytes;
  cmd, res : string;
  bcc : Byte;
  len : integer;
  ret : integer;
  i : integer;
begin
  if Uni232C1.Connect then begin
    cmd := #02;  // STX

    cmd := cmd + '01'; // ノードNo.
    cmd := cmd + '00'; // サブアドレス = "00"
    cmd := cmd + '0';  // SID = "0"

    // 以下、FINS-mini コマンド・テキスト
    cmd := cmd + '0101'; // MRC, SRC  変数エリア読出の場合
    cmd := cmd + 'C0';   // 変数種別  "C0","C2"~"C3" のいずれか

    // "0000" = P1-P2間電圧(瞬時値) 小数点以下 1 桁
    // "0001" = P2-P3間電圧(瞬時値)小数点以下 1 桁
    // "0002" = I1電流(瞬時値)(A) 小数点以下 2 桁
    // "0003" = I2電流(瞬時値)(A) 小数点以下 2 桁
    // "0004" = 有効電力(瞬時値)(kW) 小数点以下 2 桁
    // "0005" (欠番)
    // "0006" = 力率 (瞬時値) 小数点以下 2 桁
    // "0007" = 周波数(Hz)  小数点以下 1 桁
    // "0008" = 積算電力量(kWh) 小数点以下 1 桁
    // "0009" (欠番)
    // "000A" = ステータス
    // "000B" = バージョン
    cmd := cmd + '0000'; // 読み出し開始アドレス
    cmd := cmd + '00';   // ビット位置 "00" 固定
    cmd := cmd + '000B'; // 読み出し数(16進表記)最大 11 ($0B) 個まで  Len = 105( 8 * 11 +17 )

    cmd := cmd + #03; // ETX

    txData := TEncoding.ANSI.GetBytes(cmd);
    len := Length(txData);
    // BCC (ブロックチェックキャラクタ)計算
    // STX の次 ノードNo. から ETX までの XOR
    bcc := 0;
    for i := 1 to len - 1 do // ETX を含む
      bcc := bcc xor txData[i];
    SetLength(txData, len + 1);
    txData[len] := bcc; // 最後に BCC を追加
    // 送信
    Uni232C1.Write(len + 1, @txData[0]);

    Sleep(10);
    SetLength(rxData, 128); // 受信データバイト数ピッタリ+1で良い
    // 受信
    ret := Uni232C1.Read(128, @rxData[0]);
    Memo1.Lines.Add('Len = ' + ret.ToString); // 受信バイト数
    // 正常時
    //  バイト数 = データ数 * 8 + 14 + 2(STX,ETX) + 1(#0)
    //   データ数が2の時は 2 * 8 + 17 = 33
    // BCC エラー時
    //  バイト数 = 9 ;  STX + "0100" (ノードNo.+ サブアドレス) + "13"(BCCエラー) + ETX + #0

    res := TEncoding.ANSI.GetString(rxData);
    res := Trim(res); // 制御文字を含まない
    Memo1.Lines.Add(res);
    if ret >= 8 * 11 + 17 then begin // データ数 = 11 の時
      // "01" ノードNo.
      // "00" サブアドレス
      // "00" 終了コード "00" であれば正常終了
      // "01" MRC,
      // "01" SRC
      // "00" MRES
      // "00" SRES
      // "000003FA" P1-P2 間の電圧   $3FA = 1018 これの 1/10 が電圧値 = 101.8 V 
      // "00000000" P2-P3 間の電圧
      // ....以降データ数に応じて、8 桁 16 進表記の文字が続く
      Memo1.Lines.Add('P1-P2 間電圧 = ' +
        Format('%.1f V', [StrToIntDef('$' + Copy(res, 15, 8), 0) * 0.1]));
      Memo1.Lines.Add('P2-P3 間電圧 = ' +
        Format('%.1f V', [StrToIntDef('$' + Copy(res, 23, 8), 0) * 0.1]));
      Memo1.Lines.Add('I1 電流 = ' +
        Format('%.2f A', [StrToIntDef('$' + Copy(res, 31, 8), 0) * 0.01]));
      Memo1.Lines.Add('I2 電流 = ' +
        Format('%.2f A', [StrToIntDef('$' + Copy(res, 39, 8), 0) * 0.01]));
      Memo1.Lines.Add('有効電力 = ' +
        Format('%.2f kW', [StrToIntDef('$' + Copy(res, 47, 8), 0) * 0.01]));
      // "0005" 欠番のデータは無視
      Memo1.Lines.Add('力率 = ' +
        Format('%.2f ', [StrToIntDef('$' + Copy(res, 63, 8), 0) * 0.01]));
      Memo1.Lines.Add('周波数 = ' +
        Format('%.1f Hz', [StrToIntDef('$' + Copy(res, 71, 8), 0) * 0.1]));
      Memo1.Lines.Add('積算電力量 = ' +
        Format('%.1f kWh', [StrToIntDef('$' + Copy(res, 79, 8), 0) * 0.1]));
    end;
  end;
end;

end.