Delphi Modbus RTU Master 2019/08/19 ~ 2019/11/07
・2019/08/16 初版作成
・2019/08/19 受信廻りのコードを修正、整理(基本的な変更はありません)
・2019/11/07 三菱インバータ FR-A820 の情報を追加
Modbus RTU テスト用のツールです。
パソコン側がマスター、接続機器がスレーブになります。
読み出しコマンド(03H)と書き込みコマンド(10H)のみ使用。通信データは16 ワードです。
16 データの読み込み、16 データまたは 1 データの書き込みが行えます。
アドレスのデータが書き込み不可の場合は、異常メッセージが表示されます。
16 データの書き込みの場合、書き込み許可のアドレスのデータのみが変更されます。
・動作確認には、azbil 調節計 SDC35 と YOKOGAWA 調節計 UT32A を使用しました。
・RS485 - USB 変換には、TTL-RS485 変換モジュールと UART-USB 変換モジュールを組み合わせて使用しました。
・2019/11/07 三菱インバータ FR-A820 で試してみました。
三相電源が無い場合、+24, SD 端子に外部から DC24V を給電すると、設定の他 RS-485 のテストが行えます。
パラメータ Pr.549 = 1 (MODBUS RTU プロトコル)、Pr.331 = 1 (RS-485 通信局番)に変更して、一度電源断(パワーオンリセット)。
9600bps、EVEN、1 StopBits でつながります。正しく(初期値)は、2 StopBits のようですが、1 でもつながり、
アドレス 0400H あたりで何か数値が表示されました。アドレスが不正の場合は、エラー表示されます。
市販の USB-RS485 変換器を 2 線で使用しています。
■使い方
・通信(Comport)設定を行います。
・接続先の機器 ID を入力し[Enter] キーを押すかするか、UpDown ボタンをクリックすると、接続機器から 16 データが読み込まれます。
・アドレスを入力し、[Enter] キーを押すか、[READ (03H)] ボタンをクリックすると、接続機器から16 データが読み込まれます。
・値を変更する場合は、「WRITE (DEC)」 欄に数値(10進)を入力し、[Enter]キーを押すか、[WRITE (10H) 1 データ]
ボタンをクリックすると、接続機器に書き込まれます。
複数のデータを書き込む場合は、数値を入力し、[WRITE (10H) 16 データ] ボタンをクリックします。
書き込み不可のアドレスが含まれていると異常メッセージが表示されます。書き込み許可のアドレスのデータのみ変更されます。
※読み込み ID と違う場合は書き込まないようにしていますが、念のために「接続 ID」、「先頭アドレス」を確認してください。
■ダウンロード
ModbusRtu.zip (exe Ver. 1.01 本体 のみ)
■ソースコード (Delphi 10.3 Community Edition)
{ 2019/08/16 初版作成 2019/08/19 受信廻りのコードを整理、修正 } unit Unit4; interface uses Winapi . Windows, Winapi . Messages, System . SysUtils, System . Variants, System . Classes, Vcl . Graphics, Vcl . Controls, Vcl . Forms, Vcl . Dialogs, Vcl . StdCtrls, OoMisc, AdPort, Vcl . Grids, AdSelCom, Vcl . Buttons, IniFiles, Vcl . ExtCtrls, Vcl . ComCtrls; type TForm4 = class (TForm) ApdComPort1: TApdComPort; StringGrid1: TStringGrid; Edit4: TEdit; Label1: TLabel; Edit5: TEdit; Edit6: TEdit; CheckBox1: TCheckBox; Label2: TLabel; Edit7: TEdit; Label3: TLabel; ComboBox1: TComboBox; ComboBox2: TComboBox; ComboBox3: TComboBox; ComboBox4: TComboBox; ComboBox5: TComboBox; SpeedButton1: TSpeedButton; SpeedButton2: TSpeedButton; SpeedButton3: TSpeedButton; SpeedButton4: TSpeedButton; SpeedButton5: TSpeedButton; SpeedButton6: TSpeedButton; Label4: TLabel; Edit1: TEdit; Label5: TLabel; Edit2: TEdit; Edit3: TEdit; Label6: TLabel; BitBtn1: TBitBtn; BitBtn2: TBitBtn; BitBtn3: TBitBtn; Timer1: TTimer; UpDown1: TUpDown; procedure ApdComPort1TriggerAvail(CP: TObject; Count: Word ); procedure FormCreate(Sender: TObject); procedure CheckBox1Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure SpeedButton1Click(Sender: TObject); procedure Edit4KeyPress(Sender: TObject; var Key: Char ); procedure BitBtn1Click(Sender: TObject); procedure BitBtn2Click(Sender: TObject); procedure BitBtn3Click(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure StringGrid1KeyPress(Sender: TObject; var Key: Char ); procedure StringGrid1Click(Sender: TObject); procedure UpDown1Click(Sender: TObject; Button: TUDBtnType); private { Private 宣言 } public { Public 宣言 } buf : string ; cmdMode : integer ; mbId : Byte ; // 機器のID mbFuncCode : Byte ; // Modbus function code mbBytes : Byte ; // データのバイト数s mbErrorCode : Byte ; // エラーコード txbuf : array [ 0..41 ] of Byte ; // 送信用バッファ rxBuf : array [ 0..36 ] of Byte ; // 受信用バッファ rxIndex : integer ; // カウンター edIndex : integer ; // データの終端 readId : Byte ; // Read時のID readAdr : Word ; // Read時のアドレス readData : array [ 0..15 ] of word ; // 受信データ格納 writeData : array [ 0..15 ] of word ; // 送信データ格納 DIbuf : array [ 0..15 ] of bool; // Digital Input // Comport 設定 procedure setComPortParams; procedure clearEdits; end ; var Form4: TForm4; implementation {$R *.dfm} // n の k 乗 (Math ユニット不要) function IntPower(n, k : integer ): integer ; var i : integer ; begin result := 1 ; for i := 1 to k do result := result * n; end ; function CRC16(buf : array of Byte ; BufLen: integer ): word ; // crc16 計算 // azbil SDC 取扱説明書 詳細編 CRC計算 を pascal に移植 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 . setComPortParams; // Comport 設定 var s : string ; begin with ComboBox1 do if ItemIndex >= 0 then begin s := Copy(Items[ItemIndex], 4 ); ApdComPort1 . ComNumber := StrToIntDef(s, 0 ); end ; with ComboBox2 do if ItemIndex >= 0 then ApdComPort1 . Baud := StrToIntDef(Items[ItemIndex], 19200 ); with ComboBox3 do if ItemIndex >= 0 then ApdComPort1 . DataBits := ItemIndex + 7 ; with ComboBox4 do if ItemIndex >= 0 then ApdComPort1 . Parity := TParity(ItemIndex); with ComboBox5 do if ItemIndex >= 0 then ApdComPort1 . StopBits := ItemIndex + 1 ; end ; procedure TForm4 . clearEdits; begin Edit1 . Text := '' ; Edit2 . Text := '' ; Edit3 . Text := '' ; Edit5 . Text := '' ; Edit6 . Text := '' ; end ; procedure TForm4 . SpeedButton1Click(Sender: TObject); // アドレス UP/DOWN var adr : integer ; stp : integer ; btn : TSpeedButton; begin btn := Sender as TSpeedButton; // データの先頭アドレス if CheckBox1 . Checked then begin stp := StrToInt(Copy(btn . Caption, 1 , 1 ) + '$' + Copy(btn . Caption, 2 )); adr := StrToIntDef( '$' + Edit4 . Text, 0 ); adr := adr + stp; if adr > $ffff then adr := $ffff ; if adr < 0 then adr := 0 ; Edit4 . Text := IntToHex(adr, 4 ); end else begin stp := StrToInt(btn . Caption); adr := StrToIntDef(Edit4 . Text, 0 ); adr := adr + stp; if adr > $ffff then adr := $ffff ; if adr < 0 then adr := 0 ; Edit4 . Text := IntToStr(adr); end ; BitBtn2Click(self); end ; procedure TForm4 . StringGrid1Click(Sender: TObject); begin // COL = 4 のみ編集許可 with StringGrid1 do if Col = 4 then Options := Options + [goEditing] else Options := Options - [goEditing]; end ; procedure TForm4 . StringGrid1KeyPress(Sender: TObject; var Key: Char ); begin // Enter キーで再読み込み if Key = # 13 then begin Key := # 0 ; with StringGrid1 do begin if (Col = 4 ) and (Cells[Col, Row] <> '' ) then begin BitBtn1Click(self); end ; end ; end ; end ; procedure TForm4 . Timer1Timer(Sender: TObject); begin // 再読み込み if cmdMode = 101 then begin Timer1 . Enabled := false ; BitBtn2Click(self); StringGrid1 . SetFocus; end ; end ; procedure TForm4 . UpDown1Click(Sender: TObject; Button: TUDBtnType); begin // 再読み込み BitBtn2Click(self); end ; procedure TForm4 . ApdComPort1TriggerAvail(CP: TObject; Count: Word ); var i, j, k : Word ; B : array [ 0..511 ] of Byte ; s : string ; begin ApdComPort1 . GetBlock(B, Count); // 受信バイナリデータを文字列にする for i := 0 to Count - 1 do begin buf := buf + IntToHex(B[i], 2 ); rxBuf[rxIndex] := B[i]; case rxIndex of 0 : begin mbId := B[i]; Edit1 . Text := IntToStr(mbId); end ; 1 : begin mbFuncCode := B[i]; Edit2 . Text := IntToHex(mbFuncCode, 2 ); end ; end ; Inc(rxIndex); if rxIndex > 2 then begin if rxIndex = 3 then begin mbErrorCode := 0 ; // エラーコードをクリア Edit3 . Text := '' ; if mbFuncCode and $80 > 0 then begin mbBytes := 0 ; mbErrorCode := rxBuf[ 2 ]; // 異常 case mbErrorCode of 0 : s := '' ; 1 : s := 'スレーブは当該ファンクションをサポートしていない' ; 2 : s := '指定されたデータアドレスは、スレーブには存在しない' ; 3 : s := '指定されたデータは、許されない' ; else s := 'その他の異常' ; end ; Edit3 . Text := s; if s <> '' then begin Edit3 . Text := s; ShowMessage(s); end ; edIndex := 4 ; end // 正常 else begin mbBytes := rxBuf[ 2 ]; case mbFuncCode of 1..4 : edIndex := mbBytes + 4 ; 5 , 6 , 8 , $0B , $0F , $10 : edIndex := 7 ; end ; end ; end ; // 最終まで取得終了 if rxIndex > edIndex then begin if mbFuncCode <= $02 then begin for j := 0 to mbBytes - 1 do begin with StringGrid1 do begin Cells[ 3 , j * 8 + 1 ] := IntToHex(rxBuf[ 3 + j], 2 ); for k := 0 to 15 do begin DIBuf[k] := rxBuf[ 3 + j] and IntPower( 2 , k) > 0 ; if DIbuf[k] then Cells[ 2 , j * 8 + k + 1 ] := '1' else Cells[ 2 , j * 8 + k + 1 ] := '0' ; end ; end ; if j > 0 then break; end ; end ; if (mbFuncCode = $03 ) or (mbFuncCode = $04 ) then begin for j := 0 to mbBytes div 2 - 1 do begin readData[j] := rxBuf[ 3 + j * 2 ] shl 8 + rxBuf[ 3 + j * 2 + 1 ]; with StringGrid1 do begin Cells[ 2 , j + 1 ] := IntToStr(readData[j]); Cells[ 3 , j + 1 ] := IntToHex(readData[j], 4 ); end ; if j >= 15 then break; end ; // データ数が 16 に満たない時 if mbBytes div 2 <= 15 then begin for j := mbBytes div 2 to 15 do with StringGrid1 do begin Cells[ 2 , j + 1 ] := '' ; Cells[ 3 , j + 1 ] := '' ; end ; end ; end ; // 受信データを 16 進で表示 buf := '' ; for j := 0 to rxIndex - 1 do buf := buf + IntToHex(rxBuf[j], 2 ); Edit6 . Text := buf; rxIndex := 0 ; end ; end ; end ; end ; procedure TForm4 . BitBtn1Click(Sender: TObject); // WRITE (10H) x 1 データ // 書き込みはリストの上から1個のみ // 書き込み不可のアドレスでは、データ異常になる var i : integer ; adr : integer ; s : string ; crc : integer ; flag : boolean ; id : byte ; sgRow : integer ; begin clearEdits; sgRow := 0 ; // 機器 ID id := StrToIntDef(Edit7 . Text, 1 ); // データの先頭アドレス if CheckBox1 . Checked then adr := StrToIntDef( '$' + Edit4 . Text, - 1 ) else adr := StrToIntDef(Edit4 . Text, - 1 ); if adr < 0 then ShowMessage( 'アドレスが不正です.' ) else begin if (id = readId) and (adr = readAdr) then flag := True else begin ShowMessage( '読み込み時のID, アドレスが異なります.' ); flag := false ; end ; if flag then begin with StringGrid1 do begin // データが読み込まれているか for i := 1 to 16 do begin if (Cells[ 2 , i] = '' ) or (Cells[ 3 , i] = '' ) then begin ShowMessage( 'データが読み込まれていません.' ); flag := false ; break; end ; end ; // 書き込みデータがあるか if flag then begin flag := false ; for i := 1 to 16 do begin if (Cells[ 4 , i] <> '' ) then begin flag := True ; sgRow := i; break; end ; end ; end ; if not flag then ShowMessage( '書き込みデータがありません.' ) else begin writeData[ 0 ] := StrToInt(Cells[ 4 , sgRow]); adr := StrToInt( '$' + Cells[ 1 , sgRow]); //HEX ADR end ; end ; end ; if flag then begin txBuf[ 0 ] := id; // 機器アドレス txBuf[ 1 ] := $10 ; // ファンクション番号 txBuf[ 2 ] := adr shr 8 ; // 先頭アドレス上位 txBuf[ 3 ] := adr and $ff ; // 先頭アドレス下位 txBuf[ 4 ] := 0 ; // 書き込みデータ数上位 txBuf[ 5 ] := 1 ; // 書き込みデータ数下位 txBuf[ 6 ] := 2 ; // 書き込みバイト数 = データ数 x 2 txBuf[ 7 ] := writeData[ 0 ] shr 8 ; // 書き込みデータ上位 txBuf[ 8 ] := writeData[ 0 ] and $ff ; // 書き込みデータ下位 crc := CRC16(txBuf, 9 ); txBuf[ 9 ] := crc shr 8 ; // crc上位 txBuf[ 10 ] := crc and $ff ; // crc下位 with ApdComport1 do begin setComPortParams; try Open := True ; rxIndex := 0 ; PutBlock(txBuf, 11 ); Sleep( 10 ); s := '' ; for i := 0 to 10 do s := s + IntToHex(txBuf[i], 2 ); Edit5 . Text := s; with StringGrid1 do begin ShowMessage( '機器ID : ' + Edit7 . Text + # 13 + 'アドレス : ' + Cells[ 0 , sgRow] + ' (hex = ' + Cells[ 1 , sgRow] + ')' + # 13 + 'データ : [ ' + Cells[ 2 , sgRow] + ' ] -> [ ' + Cells[ 4 , sgRow] + ' ]' + # 13 + '送信しました. 再読み込みします.' ); Cells[ 4 , sgRow] := '' ; end ; // 再読み込み cmdMode := 101 ; Timer1 . Interval := 500 ; Timer1 . Enabled := True ; except end ; end ; end ; end ; end ; procedure TForm4 . BitBtn2Click(Sender: TObject); // READ (03H) 16 データ var adr : integer ; crc : integer ; s : string ; i: integer ; id : byte ; begin clearEdits; // 機器 ID id := StrToIntDef(Edit7 . Text, 1 ); // データの先頭アドレス if CheckBox1 . Checked then adr := StrToIntDef( '$' + Edit4 . Text, - 1 ) else adr := StrToIntDef(Edit4 . Text, - 1 ); if adr < 0 then ShowMessage( 'アドレスが不正です.' ) else begin with StringGrid1 do begin for i := 1 to 16 do begin Cells[ 0 , i] := IntToStr(adr + i - 1 ); Cells[ 1 , i] := IntToHex(adr + i - 1 , 4 ); Cells[ 2 , i] := '' ; Cells[ 3 , i] := '' ; end ; end ; txBuf[ 0 ] := id; // 機器アドレス txBuf[ 1 ] := 3 ; // ファンクション番号 txBuf[ 2 ] := adr shr 8 ; // 先頭アドレス上位 txBuf[ 3 ] := adr and $ff ; // 先頭アドレス下位 txBuf[ 4 ] := 0 ; // 読み出し数上位 txBuf[ 5 ] := 16 ; // 読み出し数下位 crc := CRC16(txBuf, 6 ); txBuf[ 6 ] := crc shr 8 ; // crc上位 txBuf[ 7 ] := crc and $ff ; // crc下位 with ApdComport1 do begin setComPortParams; try Open := True ; rxIndex := 0 ; PutBlock(txBuf, 8 ); Sleep( 10 ); s := '' ; for i := 0 to 7 do s := s + IntToHex(txBuf[i], 2 ); Edit5 . Text := s; // データ受信のIDと先頭アドレスを保持 readAdr := adr; readId := id; except end ; end ; end ; end ; procedure TForm4 . BitBtn3Click(Sender: TObject); // WRITE (10H) x 16 データ // 書き込み不可のアドレスでは、データ異常になる var i : integer ; adr : integer ; s : string ; crc : integer ; flag : boolean ; id : byte ; begin clearEdits; // 機器 ID id := StrToIntDef(Edit7 . Text, 1 ); // データの先頭アドレス if CheckBox1 . Checked then adr := StrToIntDef( '$' + Edit4 . Text, - 1 ) else adr := StrToIntDef(Edit4 . Text, - 1 ); if adr < 0 then ShowMessage( 'アドレスが不正です.' ) else begin if (id = readId) and (adr = readAdr) then flag := True else begin ShowMessage( '読み込み時のID, アドレスが異なります.' ); flag := false ; end ; if flag then begin with StringGrid1 do begin // データが読み込まれているか for i := 1 to 16 do begin if (Cells[ 2 , i] = '' ) or (Cells[ 3 , i] = '' ) then begin ShowMessage( 'データが読み込まれていません.' ); flag := false ; break; end ; end ; // 書き込みデータがあるか if flag then begin flag := false ; for i := 1 to 16 do begin if (Cells[ 4 , i] <> '' ) then begin flag := True ; break; end ; end ; end ; if not flag then ShowMessage( '書き込みデータがありません.' ) else for i := 1 to 16 do if Cells[ 4 , i] <> '' then writeData[i- 1 ] := StrToInt(Cells[ 4 , i]) else writeData[i- 1 ] := readData[i- 1 ]; end ; end ; if flag then begin txBuf[ 0 ] := id; // 機器アドレス txBuf[ 1 ] := $10 ; // ファンクション番号 txBuf[ 2 ] := adr shr 8 ; // 先頭アドレス上位 txBuf[ 3 ] := adr and $ff ; // 先頭アドレス下位 txBuf[ 4 ] := 0 ; // 書き込みデータ数上位 txBuf[ 5 ] := 16 ; // 書き込みデータ数下位 txBuf[ 6 ] := 32 ; // 書き込みバイト数 = データ数 x 2 for i := 0 to 15 do begin txBuf[ 7 + i * 2 ] := writeData[i] shr 8 ; // 書き込みデータ上位 txBuf[ 8 + i * 2 ] := writeData[i] and $ff ; // 書き込みデータ下位 end ; crc := CRC16(txBuf, 7 + 32 ); txBuf[ 7 + 32 ] := crc shr 8 ; // crc上位 txBuf[ 8 + 32 ] := crc and $ff ; // crc下位 // 最終はtxbuf[40] with ApdComport1 do begin setComPortParams; try Open := True ; rxIndex := 0 ; PutBlock(txBuf, 41 ); Sleep( 10 ); s := '' ; for i := 0 to 40 do s := s + IntToHex(txBuf[i], 2 ); Edit5 . Text := s; ShowMessage( '送信しました. 再読み込みします.' ); with StringGrid1 do for i := 1 to 16 do Cells[ 4 , i] := '' ; // 再読み込み cmdMode := 101 ; Timer1 . Interval := 500 ; Timer1 . Enabled := True ; except end ; end ; end ; end ; end ; procedure TForm4 . CheckBox1Click(Sender: TObject); var n : integer ; begin if CheckBox1 . Checked then begin n := StrToIntDef(Edit4 . Text, - 1 ); if n < 0 then ShowMessage( 'アドレスが不正です.' ) else Edit4 . Text := IntToHex(n, 4 ); end else begin n := StrToIntDef( '$' + Edit4 . Text, - 1 ); if n < 0 then ShowMessage( 'アドレスが不正です.' ) else Edit4 . Text := IntToStr(n); end ; end ; procedure TForm4 . Edit4KeyPress(Sender: TObject; var Key: Char ); begin if Key = # 13 then begin Key := # 0 ; BitBtn2Click(self); end ; end ; procedure TForm4 . FormCreate(Sender: TObject); var i : integer ; ini : TIniFile; comNo : integer ; adr : word ; begin clearEdits; BitBtn3 . Caption := 'WRITE (10H)' + # 13 # 10 + '16 データ' ; BitBtn1 . Caption := 'WRITE (10H)' + # 13 # 10 + '1 データ' ; BitBtn2 . Caption := 'READ (03H)' + # 13 # 10 + '16 データ' ; ini := TIniFile . Create(ChangeFileExt(ParamStr( 0 ), '.ini' )); try comNo := ini . ReadInteger( 'Com' , 'PortNo' , 0 ); with ComboBox2 do ItemIndex := Items . IndexOf(IntToStr(ini . ReadInteger( 'Com' , 'Baud' , 19200 ))); ComboBox3 . ItemIndex := ini . ReadInteger( 'Com' , 'DataBits' , 8 ) - 7 ; ComboBox4 . ItemIndex := ini . ReadInteger( 'Com' , 'Parity' , 2 ); ComboBox5 . ItemIndex := ini . ReadInteger( 'Com' , 'StopBits' , 1 ) - 1 ; UpDown1 . Position := ini . ReadInteger( 'Modbus' , 'IDNUM' , 1 ); CheckBox1 . Checked := ini . ReadBool( 'Modbus' , 'HEX' , true ); adr := ini . ReadInteger( 'Modbus' , 'ADRDEC' , 9000 ); if not CheckBox1 . Checked then Edit4 . Text := IntToStr(adr) else Edit4 . Text := IntToHex(adr, 4 ); finally ini . Free; end ; AdSelCom . ShowPortsInUse := False ; with ComboBox1 do begin // 使用可能な Comport を検索 for i := 1 to 64 do if AdSelCom . IsPortAvailable(i) then Items . Add (AdPort . ComName(i)); if Items . Count > 0 then ItemIndex := Items . IndexOf( 'COM' + IntToStr(comNo)); end ; with StringGrid1 do begin Cells[ 0 , 0 ] := 'ADR(DEC)' ; Cells[ 1 , 0 ] := 'ADR(HEX)' ; Cells[ 2 , 0 ] := 'READ(DEC)' ; Cells[ 3 , 0 ] := 'READ(HEX)' ; Cells[ 4 , 0 ] := 'WRITE(DEC)' ; end ; end ; procedure TForm4 . FormDestroy(Sender: TObject); var ini : TIniFile; comNo, baud : integer ; adr : word ; begin if CheckBox1 . Checked then adr := StrToIntDef( '$' + Edit4 . Text, 0 ) else adr := StrToIntDef(Edit4 . Text, 0 ); ini := TIniFile . Create(ChangeFileExt(ParamStr( 0 ), '.ini' )); try comNo := 0 ; with ComboBox1 do if ItemIndex >= 0 then comNo := StrToIntDef(Copy(Items[ItemIndex], 4 ), 0 ); ini . WriteInteger( 'Com' , 'PortNo' , comNo); baud := 19200 ; with ComboBox2 do if ItemIndex >= 0 then baud := StrToIntDef(Items[ItemIndex], 19200 ); ini . WriteInteger( 'Com' , 'Baud' , baud); ini . WriteInteger( 'Com' , 'DataBits' , ComboBox3 . ItemIndex+ 7 ); ini . WriteInteger( 'Com' , 'Parity' , ComboBox4 . ItemIndex); ini . WriteInteger( 'Com' , 'StopBits' , ComboBox5 . ItemIndex + 1 ); ini . WriteInteger( 'Modbus' , 'IDNUM' , UpDown1 . Position); ini . WriteInteger( 'Modbus' , 'ADRDEC' , adr); ini . WriteBool( 'Modbus' , 'HEX' , CheckBox1 . Checked); finally ini . Free; end ; end ; end . |