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.