ADS sum command: reading or writing several variables with Delphi
System requirements:
- TwinCAT v2.11 Build >= 1550;
Task
Read or write several PLC variables with TcAdsClient.ReadWrite command. This sample shows also how to get and enable several PLC variables handles with a ADS sum command.
Description
The singles sub commands are moved in an AdsStream (Write Data) to the PLC and executed there in a row. The result is moved with a further AdsStream (Return value + Read Data) from the PLC to the client.
Dynamic arrays can be used to assign a variable number of parameters (ADS sub commands). The Sample uses the dynamic arrays and makes the code universal reusable. Other methods are possible (array with fixed size, structure elements, etc.)
The following auxiliary methods are used as generic ADS sum commands to write or read the PLC variables value, to get or enable handles, etc. In these methods the AdsStream for the ADS sum command is filled with parameters and the ADS sum command is called by the TcAdsClient.ReadWrite command.
method BlockRead(
ig : Array of Int32;
io : Array of Int32;
var rLen : Array of Int32;
var data : AdsStream
) : Array of Int32;
method BlockWrite(
ig : Array of Int32;
io : Array of Int32;
wLen : Array of Int32;
data : AdsStream
) : Array of Int32;
method BlockReadWrite(
ig, io, wLen : Array of Int32;
wData : AdsStream;
var rLen : Array of Int32;
var rData : AdsStream
) : Array of Int32;
The generic methods are used by four more methods:
method BlockCreateVariableHandle(names : Array Of String) : Array of Int32;
method BlockDeleteVariableHandle(handles : Array of Int32) : Array of Int32;
method btnRead_Click(sender: System.Object; e: System.EventArgs);
method btnWrite_Click(sender: System.Object; e: System.EventArgs);
Delphi Prism (Embarcadero Prism XE2, Oxygen for .NET) program
namespace Sample09;
interface
uses
System.Drawing,
System.Collections,
System.Collections.Generic,
System.Windows.Forms,
System.ComponentModel, System.IO, TwinCAT.Ads;
type
/// <summary>
/// Summary description for MainForm.
/// </summary>
MainForm = partial class(System.Windows.Forms.Form)
private
adsClient : TcAdsClient;
vNames : Array of String := ["MAIN.uintValue", "MAIN.dintValue", "MAIN.boolValue"];
vLengths : Array of Int32 := [2,4,1];
vHandles : Array of Int32 := [0,0,0];
method BlockRead(ig : Array of Int32; io : Array of Int32; var rLen : Array of Int32; var data : AdsStream) : Array of Int32;
method BlockWrite(ig : Array of Int32; io : Array of Int32; wLen : Array of Int32; data : AdsStream) : Array of Int32;
method BlockReadWrite(ig, io, wLen : Array of Int32; wData : AdsStream; var rLen : Array of Int32; var rData : AdsStream) : Array of Int32;
method BlockCreateVariableHandle(names : Array Of String) : Array of Int32;
method BlockDeleteVariableHandle(handles : Array of Int32) : Array of Int32;
method MainForm_Load(sender: System.Object; e: System.EventArgs);
method MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
method btnRead_Click(sender: System.Object; e: System.EventArgs);
method btnWrite_Click(sender: System.Object; e: System.EventArgs);
protected
method Dispose(disposing: Boolean); override;
public
const ADSIGRP_SUMUP_READ = $0000F080; // AdsRW IOffs list size or 0 (=0 -> list size == WLength/3*sizeof(ULONG)) // W: {list of IGrp, IOffs, Length}
// if IOffs != 0 then R: {list of results} and {list of data}
// if IOffs == 0 then R: only data (sum result)
const ADSIGRP_SUMUP_WRITE = $0000F081; // AdsRW IOffs list size // W: {list of IGrp, IOffs, Length} followed by {list of data}
// R: {list of results}
const ADSIGRP_SUMUP_READWRITE = $0000F082;// AdsRW IOffs list size // W: {list of IGrp, IOffs, RLength, WLength} followed by {list of data}
// R: {list of results, RLength} followed by {list of data}
constructor;
end;
implementation
{$REGION Construction and Disposition}
constructor MainForm;
begin
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
end;
method MainForm.Dispose(disposing: Boolean);
begin
if disposing then begin
if assigned(components) then
components.Dispose();
//
// TODO: Add custom disposition code here
//
end;
inherited Dispose(disposing);
end;
{$ENDREGION}
Create connection
On starting the application a connection to the PLC is established and variable handles are fetched via theBlockCreateVariableHandlemethod call. The names of the three PLC variables are transferred to theBlockCreateVariableHandlemethod as a dynamic array.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Create form, connection and variable handles
method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
try
// Connect to PLC
adsClient := new TcAdsClient();
adsClient.Connect(801);
// Create variable handles
vHandles := BlockCreateVariableHandle(vNames);
// Conventional method:
// for each name in vNames index i do
// vHandles[i] := adsClient.CreateVariableHandle(name);
except
On err : AdsErrorException do
MessageBox.Show(err.Message, err.Source);
end;
end;
Close connection
On terminating the application the variable handles are released and the connection to the PLC is cut. The handles to be released are transferred to the BlockDeleteVariableHandle method as a dynamic array.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Close form and release resources
method MainForm.MainForm_FormClosing(sender: System.Object; e: System.Windows.Forms.FormClosingEventArgs);
begin
try
// Release variable handles
BlockDeleteVariableHandle(vHandles);
// Conventional method
// for each handle in vHandles index i do
// adsClient.DeleteVariableHandle(handle);
// Close connection
adsClient.Dispose()
except
On err : AdsErrorException do
MessageBox.Show(err.Message, err.Source)
end;
end;
Read PLC values
The PLC variable values are read by clicking on theReadbutton. ThebtnRead_Click event handling routing uses the generic BlockRead method. However, the instance of the AdsStream for the PLC data must be generated beforehand with a suitable size so that the values of all PLC variables fit into it. The parameters rLen(read lengths)and data (AdsStream) are transferred by reference. As a result, theBlockRead method can fill the AdsStream with data and change the actually returned data lengths (rLen) (e.g.: in case of error). If successful, the data are successively read from the AdsStream and displayed on the form.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Read block data (multiple PLC values)
method MainForm.btnRead_Click(sender: System.Object; e: System.EventArgs);
begin
var ig := new Int32[vHandles.Length];
// Calculate the size of PLC value data
var cbRead : Int32 := 0;
for each len in vLengths index i do
begin
cbRead := cbRead + len;
ig[i] := Int32(AdsReservedIndexGroups.SymbolValueByHandle);
end;
var data := new AdsStream(cbRead);
var reader := new BinaryReader(data);
var rLen := new Int32[vLengths.Length];
rLen := vLengths;
// Read PLC values
var blockResult := BlockRead(ig, vHandles, var rLen, var data);
for each adsResult in blockResult index i do
if ( adsResult <> 0 ) then
MessageBox.Show("Read failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");
data.Position := 0;
tbRuint.Text := iif((blockResult[0] = 0) And (rLen[0] = 2), reader.ReadUInt16().ToString(), "0");
tbRdint.Text := iif((blockResult[1] = 0) And (rLen[1] = 4), reader.ReadInt32().ToString(), "0");
tbRbool.Text := iif((blockResult[2] = 0) And (rLen[2] = 1), reader.ReadBoolean().ToString(), "False");
end;
Write PLC values
The values are written from the application to the PLC by clicking on the Write button. The btnWrite_Click event handling routine uses the generic BlockWrite method. The instance of the AdsStream is generated before the call with a suitable size and filled with the PLC variable values to be written.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Write block data (multiple PLC values)
method MainForm.btnWrite_Click(sender: System.Object; e: System.EventArgs);
begin
var ig := new Int32[vHandles.Length];
// Calculate the size of PLC value data
var cbWrite : Int32 := 0;
for each len in vLengths index i do
begin
cbWrite := cbWrite + len;
ig[i] := Int32(AdsReservedIndexGroups.SymbolValueByHandle);
end;
var data := new AdsStream(cbWrite);
var writer := new BinaryWriter(data);
writer.Write(UInt16.Parse(tbWuint.Text));
writer.Write(Int32.Parse(tbWdint.Text));
writer.Write(boolean.Parse(tbWbool.Text));
// Write PLC values
var blockResult := BlockWrite(ig, vHandles, vLengths, data);
for each adsResult in blockResult do
if adsResult <> 0 then
MessageBox.Show("Write failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");
end;
Create variable handles
The generic BlockReadWrite method is used to fetch the variable handles. The symbol names of the PLC variables are transferred to the BlockReadWrite method as write data in the AdsStream. The handles are received in a further AdsStream as read data. As with the BlockWrite method, the AdsStream: rData (read data) and rLen are transferred by reference here also. Only in this way can the contents of the stream be filled with data from the BlockReadWrite method. If successful, the handles are returned by the method as return parameters in the form of a dynamic array.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Create multiple symbol handles
method MainForm.BlockCreateVariableHandle( names : Array Of String ) : Array of Int32;
begin
var getHandles := new Int32[names.Length];
var ig := new Int32[names.Length];
var io := new Int32[names.Length];
var rLen := new Int32[names.Length];
var wLen := new Int32[names.Length];
var cbWrite : Int32 := 0;
for each name in names do
cbWrite := cbWrite + name.Length;
var wData := new AdsStream(cbWrite);
var writer := new BinaryWriter(wData, System.Text.Encoding.ASCII);
var rData := new AdsStream(getHandles.length * 4);
var reader := new BinaryReader(rData);
for each name in names index i do
begin
writer.Write(name.ToCharArray());// Copy PLC variable names to the stream
ig[i] := Int32(AdsReservedIndexGroups.SymbolHandleByName);// indexGroup
io[i] := 0;// indexOffset = 0
rLen[i] := sizeOf(getHandles[i]);// readLen = 4 byte
wLen[i] := name.Length;// writeLen = length of plc variable name
end;
var blockResult := BlockReadWrite(ig, io, wLen, wData, var rLen, var rData);
rData.Position := 0;
for each adsResult in blockResult index i do
begin
if (adsResult = 0) and (rLen[i] = 4) then
getHandles[i] := reader.ReadInt32()
else
MessageBox.Show("CreateVariableHandle failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");
end;
result := getHandles;
end;
Realease variable handles
The method BlockDeleteVariableHandle uses the method BlockWrite. The handles to be enabled are copied before in the AdsStream (Write-Data).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Release multiple symbol handles
method MainForm.BlockDeleteVariableHandle(handles : Array of Int32 ): Array of Int32;
begin
var data := new AdsStream(handles.Length * 4);
var writer := new BinaryWriter(data);
var ig := new Int32[handles.Length];
var wLen := new Int32[handles.Length];
for each handle in handles index i do
begin
writer.Write(handle);
ig[i] := Int32(AdsReservedIndexGroups.SymbolReleaseHandle);
wLen[i] := sizeOf(handle);// = 4 byte
end;
var blockResult := BlockWrite(ig, handles, wLen, data);
for each adsResult in blockResult do
if adsResult <> 0 then
MessageBox.Show("DeleteVariableHandle failed! Error: " + adsResult.ToString() , "TwinCAT.Ads");
result := blockResult;
end;
Generic ADS read sum command
The parameter for the ADS sum command consists of:
- IndexGroup =ADSIGRP_SUMUP_READ= 0xF080;
- IndexOffset =Number of ADS sub commands (in the sample = 3);
- readData (AdsStream) = Memory that takes read data = ListOf(Ads result) + ListOf(data);
- writeData(AdsStream)=Memory that contains data to be sent = ADS sub commands = ListOf(indexGroup + indexOffset + readLength);
Arrangement of the ADS sub command inwriteDataduring writing the PLC variables values:
Arrangement of return data inreadDataduring reading the PLC variables values:
The return values of the single ADS subcommands are extracted from thereadData-Stream and returned fromtheBlockWritemethod as return value. The read data (e.g. PLC variables values) are copied into the data method parameter.
The rLen values are aligned ( in error case the data length = Null).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Ads block read
method MainForm.BlockRead( ig : Array of Int32; io : Array of Int32; var rLen : Array of Int32; var data : AdsStream) : Array of Int32;
begin
var count := ig.Length;
var blockResult := new Int32[count];
try
var writeData := new AdsStream(count * 12);
var writer := new BinaryWriter(writeData);
var cbData : Int32 := 0;
for each len in rLen index i do
begin
writer.Write(ig[i]);
writer.Write(io[i]);
writer.Write(len);
cbData := cbData + len;
end;
if cbData <> data.Length then
MessageBox.Show("BlockRead()::Invalid read data length!", "TwinCAT.Ads");
var readData := new AdsStream((count * 4) + data.Length);
var reader := new BinaryReader(readData);
adsClient.ReadWrite(ADSIGRP_SUMUP_READ, count, readData, writeData);
readData.Position := 0;
for each adsResult in blockResult index i do
blockResult[i] := reader.ReadInt32();
// Copy read data
var copy := new BinaryWriter(data);
data.Position := 0;
for each adsResult in blockResult index i do
begin
if adsResult = 0 then// no error => copy data, error => don't copy data
copy.Write(reader.ReadBytes(rLen[i]))
else
rLen[i] := 0;
end;
except
On err : AdsErrorException do
MessageBox.Show(err.Message, err.Source)
end;
result := blockResult;
end;
Generic ADS write sum command
The parameter of the ADS write sum command consists of:
- IndexGroup =ADSIGRP_SUMUP_WRITE= 0xF081;
- IndexOffset = Number of ADS sub commands(in the sample = 3);
- readData (AdsStream) = Memory that takes read data = ListOf(Ads result);
- writeData (AdsStream) =Memory that contains data to be sent = ADS sub commands = ListOf(indexGroup + indexOffset + writeLength) + ListOf(data);
TheBlockWrite method is used in two cases ( in this sample):
- Writing the PLC variables values => the method parameterdatacontains the new PLC variables values;
- Enable the variable handles => the method parameterdatacontains handles to be enabled;
Thecontent of data is copied into writeData.
Arrangement of the ADS sub command inwriteDataduring writing the PLC variables values:
Arrangement of return data inreadDataduring reading the PLC variables values:
The return values of the single ADS subcommands are extracted from thereadData-Stream and returned fromtheBlockWritemethod as return value.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Ads block write
method MainForm.BlockWrite(ig : Array of Int32; io : Array of Int32; wLen : Array of Int32; data : AdsStream) : Array of Int32;
begin
var count := ig.Length;
var blockResult := new Int32[count];
try
var writeData := new AdsStream((count * 12 ) + data.Length);
var writer := new BinaryWriter(writeData);
var cbData : Int32 := 0;
for each len in wLen index i do
begin
writer.Write(ig[i]);
writer.Write(io[i]);
writer.Write(len);
cbData := cbData + len;
end;
if cbData <> data.Length then
MessageBox.Show("BlockWrite()::Invalid write data length!", "TwinCAT.Ads");
writer.Write(data.ToArray());
var readData := new AdsStream(count * 4);
var reader := new BinaryReader(readData);
adsClient.ReadWrite(ADSIGRP_SUMUP_WRITE, count, readData, writeData);
readData.Position := 0;
for each adsResult in blockResult index i do
blockResult[i] := reader.ReadInt32();
except
On err : AdsErrorException do
MessageBox.Show(err.Message, err.Source)
end;
result := blockResult;
end;
Generic ADS read/write sum command
The BlockReadWrite method is a combination ofBlockRead andBlockWritemethod. The parameter of the ADS readwrite sum command consists of:
- IndexGroup =ADSIGRP_SUMUP_READWRITE= 0xF082;
- IndexOffset = Number of ADS sub commands(in the sample = 3);
- readData(AdsStream) = Memory that takes read data = ListOf(Ads result + readLength) + ListOf(data);
- writeData (AdsStream)=Memory that contains data to be sent = ADS sub commands = ListOf(indexGroup + indexOffset + readLength + writeLength) + ListOf(data);
The BlockReadWrite method is used in this example when fetching the handles. In this case the wData method parameter contains the strings with the PLC symbol names. The symbol names are copied into writeData before the call. The handles are located in readData after the call and are copied from there into the rData method parameter. The rLen values are also adapted (in case of error the data length = zero). The return values of the individual ADS sub-commands are extracted from the readData stream and returned by the BlockReadWrite method as a return value.
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Ads block read/write
method MainForm.BlockReadWrite( ig, io, wLen : Array of Int32; wData : AdsStream;
var rLen : Array of Int32; var rData : AdsStream) : Array of Int32;
begin
var count := ig.Length;
var blockResult := new Int32[count];
try
var writeData := new AdsStream((count * 16) + wData.Length);// 16 = sizeof(indexGroup + indexOffset + readLen + writeLen)
var writer := new BinaryWriter(writeData);
var cbWData : Int32 := 0;
var cbRData : Int32 := 0;
for each len in wLen index i do
begin
writer.Write(ig[i]);
writer.Write(io[i]);
writer.Write(rLen[i]);
writer.Write(wLen[i]);
cbWData := cbWData + wLen[i];
cbRData := cbRData + rLen[i];
end;
if cbRData <> rData.Length then
MessageBox.Show("BlockReadWrite()::Invalid read data length!", "TwinCAT.Ads");
if cbWData <> wData.Length then
MessageBox.Show("BlockReadWrite()::Invalid write data length!", "TwinCAT.Ads");
writer.Write(wData.ToArray());
var readData := new AdsStream((count * 8) + rData.Length);
var reader := new BinaryReader(readData);
adsClient.ReadWrite(ADSIGRP_SUMUP_READWRITE, count, readData, writeData);
readData.Position := 0;
for each adsResult in blockResult index i do
begin
blockResult[i] := reader.ReadInt32();
rLen[i] := reader.ReadInt32();
end;
// Copy data
var copy := new BinaryWriter(rData);
rData.Position := 0;
for each adsResult in blockResult index i do
begin
if adsResult = 0 then// no error => copy data, error => don't copy data
copy.Write(reader.ReadBytes(rLen[i]));
end;
except
On err : AdsErrorException do
MessageBox.Show(err.Message, err.Source)
end;
result := blockResult;
end;
end.
PLC program
PROGRAM MAIN
VAR
uintValue : UDINT := 54321;
boolValue : BOOL := TRUE;
dintValue : DINT := -12345678;
END_VAR
;
Download
Language / IDE | Unpack the sample program |
---|---|
Delphi Prism (Embarcadero Prism XE2, Oxygen for .NET) |