YOKOGAWA UT35A/32A RS485 通信 2019/06/15
横河のデジタル指示調節計 UTAdvanced シリーズ RS485 通信の Delphi サンプルコードです。
Modbus の部分は、azbil SDC/R RS485 通信 とほぼ同じです。
RS485-USB コンバータの仮想 COM ポート経由で通信しています。シリアル通信は、ApdComport コンポーネントを使っています。
Modbus RTU 通信はバイナリデータを使用しますが、確認しやすい文字列で作成しそれをバイナリに変換しています。
受信データもバイナリを文字列に変換しています。実用の場合は、文字列に変換する必要はありません。
{ Yokogawa UT32A RS485 通信 R485 - PSL(プロトコル)を ・PCL(パソコンリンク通信チェックサムなし) ・MbASC(Modbus ASCII) ・MbRTU (Modbus RTU)(デフォルト) に設定 通信パラメータはデフォルト値を使用 Modbus ファンクションコード 0x01 ; コイル読み込み/ビット 0x02 ; 入力ステータス読み込み/ビット 0x03 ; 保持レジスタ読み込み/ワード 0x04 ; 入力レジスタ読み込み/ワード 0x05 ; 単一コイル書き込み/ビット 0x06 ; 単一保持レジスタ書き込み/ワード 0x10 ; 複数レジスタ書き込み 参考資料: 「Modbusプロトコル概説書」エム・システム技研 https://www.m-system.co.jp/mssjapanese/kaisetsu/nmmodbus.pdf } unit UT32AUnit; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, OoMisc, AdPort; type TForm4 = class(TForm) ApdComPort1: TApdComPort; Button1: TButton; Edit1: TEdit; Button2: TButton; Edit2: TEdit; Button3: TButton; Button4: TButton; Button5: TButton; Button6: TButton; Button7: TButton; procedure Button1Click(Sender: TObject); procedure ApdComPort1TriggerAvail(CP: TObject; Count: Word); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure Button5Click(Sender: TObject); procedure Button6Click(Sender: TObject); procedure Button7Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } buf : string; cmdMode : integer; // プロトコル pslType : integer; end; var Form4: TForm4; const PSL_PCL = 1001; PSL_MBASC = 1003; PSL_MBRTU = 1004; implementation {$R *.dfm} procedure TForm4.ApdComPort1TriggerAvail(CP: TObject; Count: Word); // UT32A からの受信データ var i : Word; sp : SmallInt; // マイナス表示が必要なので SmallInt 型を使用 B : array [0..511] of Byte; begin // Modbus RTU if pslType = PSL_MBRTU then begin ApdComPort1.GetBlock(B, Count); // 受信バイナリデータを文字列にする for i := 0 to Count -1 do buf := buf + IntToHex(B[i], 2); // Edit1.Text := buf; if buf.Length >= 10 then // 値のみを取得 Edit2.Text := IntToStr(StrToInt('$' + Copy(buf, 7, 4))); end; // Modbus Ascii if pslType = PSL_MBASC then begin for i := 1 to Count do buf := buf + string(ApdComPort1.GetChar); Edit1.Text := buf; // 終端(CrLf)の確認 if Copy(buf, buf.Length) = #$0A then begin // SP 取得であれば・・・ if cmdMode = 101 then begin // 16進数表記の文字列を数値に変換 sp := StrToInt('$' + Copy(buf, 8, 4)); Edit2.Text := sp.ToString; end; end; end; // PCL if pslType = PSL_PCL then begin for i := 1 to Count do buf := buf + string(ApdComPort1.GetChar); Edit1.Text := buf; // 終端(Cr)の確認 if Copy(buf, buf.Length) = #$0D then begin // 正常終了の確認 if Copy(buf, 6, 2) = 'OK' then begin // SP 取得であれば・・・ if cmdMode = 101 then begin // 終端の制御文字を外した16進数表記の文字列を数値に変換 sp := StrToInt('$' + Trim(Copy(buf, 8))); Edit2.Text := sp.ToString; end; end; end; end; end; procedure TForm4.Button1Click(Sender: TObject); // INF6 // 型番、基本仕様コード、バージョンの読み出し var cmd : string; begin ApdComPort1.ComNumber := 17; ApdComPort1.Baud := 19200; ApdComPort1.StopBits := 1; ApdComPort1.DataBits := 8; ApdComPort1.Parity := TParity.pEven; try ApdComPort1.Open := True; // プロトコル pslType := PSL_PCL; cmdMode := 0; buf := ''; // 送信 cmd := #02; // STX cmd := cmd + '01'; // アドレス番号 cmd := cmd + '01'; // CPU番号('01'に固定) cmd := cmd + '0'; // 応答待ち時間 cmd := cmd + 'INF'; // コマンド3バイト(INF6 = 型番、基本仕様コード、バージョンの読み出し) cmd := cmd + '6'; // コマンドデータ可変長 cmd := cmd + #03; // ETX cmd := cmd + #$0D; // CR ApdComPort1.PutString(cmd); // レスポンス //' 0101OKUT32ANNNR1.03.012001001120010000' // STX(02)+アドレス番号2バイト('01')+CPU番号2バイト('01')+'OK'+パラメータデータ+ETX(03)+CR($0D) // 上の例では、'OK'以降がパラメータデータ // UT32ANNNR1.03.012001001120010000 except ShowMessage('ERROR'); end; end; procedure TForm4.Button2Click(Sender: TObject); // WRD ワード単位の読み出し // 使用中の目標設定値 SP 取得 var cmd : string; begin ApdComPort1.ComNumber := 17; ApdComPort1.Baud := 19200; ApdComPort1.StopBits := 1; ApdComPort1.DataBits := 8; ApdComPort1.Parity := TParity.pEven; try ApdComPort1.Open := True; // プロトコル pslType := PSL_PCL; cmdMode := 101; buf := ''; // 送信 cmd := #02; // STX cmd := cmd + '01'; // アドレス番号 cmd := cmd + '01'; // CPU番号('01'に固定) cmd := cmd + '0'; // 応答待ち時間 cmd := cmd + 'WRD'; // コマンド 3バイト(WRD = ワード単位の読み出し) cmd := cmd + 'D2101'; // レジスタ番号 5バイト D2101=使用中の目標設定値SP cmd := cmd + ','; //','またはスペース cmd := cmd + '01'; // ワード数 2バイト cmd := cmd + #03; // ETX cmd := cmd + #$0D; // CR ApdComPort1.PutString(cmd); // レスポンス //'0101OK01F4' // STX(02)+アドレス番号2バイト('01')+CPU番号2バイト('01')+'OK'+パラメータデータ+ETX(03)+CR($0D) // 上の例では、'OK'以降がパラメータデータ // '01F4' 16進表記なので10進に変換すると設定値'500'(小数点を除いた値)が得られる // 事前に小数点位置 DP (レジスタ番号=D5202)を取得しておく except ShowMessage('ERROR'); end; end; procedure TForm4.Button3Click(Sender: TObject); // WWR ワード単位の書き込み // 使用中の目標設定値 SP の変更 var cmd : string; begin // COM ポート番号 ApdComPort1.ComNumber := 17; // UT32A デフォルト値を使用 ApdComPort1.Baud := 19200; ApdComPort1.StopBits := 1; ApdComPort1.DataBits := 8; ApdComPort1.Parity := TParity.pEven; try ApdComPort1.Open := True; // プロトコル pslType := PSL_PCL; cmdMode := 0; buf := ''; // 送信 cmd := #02; // STX cmd := cmd + '01'; // アドレス番号 cmd := cmd + '01'; // CPU番号('01'に固定) cmd := cmd + '0'; // 応答待ち時間 cmd := cmd + 'WWR'; // コマンド 3バイト(WRD = ワード単位の読み出し) cmd := cmd + 'D2101'; // レジスタ番号 5バイト D2101=使用中の目標設定値 SP cmd := cmd + ','; //','またはスペース cmd := cmd + '01'; // ワード数 2バイト cmd := cmd + ','; //','またはスペース // 書き込みデータは小数点を除いた値を16進数4桁で指定 cmd := cmd + IntToHex(250, 4); //書き込みデータ 4バイト×データ数 SP を25.0 に変更 cmd := cmd + #03; // ETX cmd := cmd + #$0D; // CR ApdComPort1.PutString(cmd); // レスポンス //'0101OK' // STX(02)+アドレス番号2バイト('01')+CPU番号2バイト('01')+'OK'+ETX(03)+CR($0D) except ShowMessage('ERROR'); end; end; procedure TForm4.Button4Click(Sender: TObject); // WRD ワード単位の読み出し // 小数点位置 の取得 var cmd : string; begin ApdComPort1.ComNumber := 17; ApdComPort1.Baud := 19200; ApdComPort1.StopBits := 1; ApdComPort1.DataBits := 8; ApdComPort1.Parity := TParity.pEven; try ApdComPort1.Open := True; // プロトコル pslType := PSL_PCL; cmdMode := 0; buf := ''; // 送信 cmd := #02; // STX cmd := cmd + '01'; // アドレス番号 cmd := cmd + '01'; // CPU番号('01'に固定) cmd := cmd + '0'; // 応答待ち時間 cmd := cmd + 'WRD'; // コマンド 3バイト(WRD = ワード単位の読み出し) cmd := cmd + 'D5202'; // レジスタ番号 5バイト D5202=小数点位置 P.DP_L1 cmd := cmd + ','; //','またはスペース cmd := cmd + '01'; // ワード数 2バイト cmd := cmd + #03; // ETX cmd := cmd + #$0D; // CR ApdComPort1.PutString(cmd); // レスポンス //'0101OK0001' // STX(02)+アドレス番号2バイト('01')+CPU番号2バイト('01')+'OK'+パラメータデータ+ETX(03)+CR($0D) // 上の例では、'OK'以降がパラメータデータ except ShowMessage('ERROR'); end; end; procedure TForm4.Button5Click(Sender: TObject); // WRD ワード単位の読み出し // 測定値 PV_L1 の取得 var cmd : string; begin ApdComPort1.ComNumber := 17; ApdComPort1.Baud := 19200; ApdComPort1.StopBits := 1; ApdComPort1.DataBits := 8; ApdComPort1.Parity := TParity.pEven; try ApdComPort1.Open := True; // プロトコル pslType := PSL_PCL; cmdMode := 101; buf := ''; // 送信 cmd := #02; // STX cmd := cmd + '01'; // アドレス番号 cmd := cmd + '01'; // CPU番号('01'に固定) cmd := cmd + '0'; // 応答待ち時間 cmd := cmd + 'WRD'; // コマンド 3バイト(WRD = ワード単位の読み出し) cmd := cmd + 'D2003'; // レジスタ番号 5バイト D2003= PV_L1 cmd := cmd + ','; //','またはスペース cmd := cmd + '01'; // ワード数 2バイト cmd := cmd + #03; // ETX cmd := cmd + #$0D; // CR ApdComPort1.PutString(cmd); except ShowMessage('ERROR'); end; end; function CRC16(buf : array of Byte; BufLen: integer): word; var i, j : integer; carry : word; crc : word; crcl : Byte; begin crc := $FFFF; for i := 0 to BufLen - 1 do begin crc := word(crc xor Ord(Buf[i])); for j := 0 to 7 do begin carry := crc and $0001; crc := crc shr 1; if carry = 1 then crc := crc xor $A001; end; end; crcl := (crc and $FF00) shr 8; crc := crc shl 8; crc := crc or crcl; result := crc; end; procedure TForm4.Button6Click(Sender: TObject); // Modbus RTU // SP 読み出し var s : string; i : integer; TxBuf : array [0..255] of Byte; TxBufLen : integer; crc : word; begin ApdComPort1.ComNumber := 17; ApdComPort1.Baud := 19200; ApdComPort1.StopBits := 1; ApdComPort1.DataBits := 8; ApdComPort1.Parity := TParity.pEven; try ApdComPort1.Open := True; s := '01'; // 機器アドレス(2桁) s := s + '03'; // 複数レジスタの値読み出しコマンド // D2101 の Ref,No 42101 - 40001 = 2100 を16進表記に s := s + '0834'; //読み出し先頭アドレス(16進4桁)H No. s := s + '0001'; //読み出し数(4桁) TxBufLen := s.Length div 2; for i := 1 to TxBufLen do TxBuf[i-1] := StrToInt('$' + s[i * 2 - 1] + s[i * 2]); // チェックサム(CRC)計算 crc := CRC16(TxBuf, TxBufLen); Edit1.Text := s; s := IntToHex(crc, 4); // 文字列として表示 Edit1.Text := Edit1.Text + s; // 送信データに CRC を追加 TxBuf[TxBufLen ] := StrToInt('$' + Copy(s, 1, 2)); TxBuf[TxBufLen + 1] := StrToInt('$' + Copy(s, 3, 2)); TxBufLen := TxBufLen + 2; // プロトコル pslType := PSL_MBRTU; buf := ''; // 送信 ApdComPort1.PutBlock(TxBuf, TxBufLen); // レスポンス='01030200FA3807' // アドレス番号:01 + ファンクションコード:03 + データバイト数:02 + データ:00FA // 最後の4文字はエラーチェック // 先頭 7 文字目から 4 文字(16進)$00FA = 250 except ShowMessage('ERROR'); end; end; procedure TForm4.Button7Click(Sender: TObject); // Modbus ASCII // SP 読み出し const CRLF = #$0D#$0A; var s, head, chksum : string; i : integer; sum : integer; begin ApdComPort1.ComNumber := 17; ApdComPort1.Baud := 19200; ApdComPort1.StopBits := 1; ApdComPort1.DataBits := 7; // ASCII は 7 Bits ApdComPort1.Parity := TParity.pEven; try ApdComPort1.Open := True; head := ':'; // 電文先頭 s := '01'; // 機器アドレス(2桁) s := s + '03'; // 読み出しコマンド // D2101 の Ref,No 42101 - 40001 = 2100 を16進表記に s := s + '0834'; //読み出し先頭アドレス(16進4桁)H No. s := s + '0001'; //読み出し数(4桁) // チェックサム(LRC)の作成 // 機器アドレスの先頭からチェックサムの直前まで sum := 0; // 16進2文字ずつを数値に変換し、加算する for i := 1 to s.Length div 2 do sum := sum + StrToInt('$' + Copy(s, i * 2 - 1, 2)); // 加算結果の下位1バイト sum := sum and $000000FF; // 2の補数 sum := (- sum and $000000FF); // それを2バイトの ASCII コードに変換 chksum := IntToHex(sum, 2); s := s + chksum; // チェックサム s := s + CRLF; // 電文の最後 // プロトコル pslType := PSL_MBASC; cmdMode := 101; buf := ''; // 送信 ApdComPort1.PutString(head + s); // ':010302012CCD'+CRLF // ':' + アドレス番号:01 + ファンクションコード:03 + データバイト数:02 + データ:012C + CRLF // 先頭 8 文字目から 4 文字(16進)$012C = 300 // 終端 CRLF は $0D0A except ShowMessage('ERROR'); end; end; end.