ADS sum command: read and write multiple variables with an ADS command.
Requirements:
- TwinCAT v2.10 Build >= 1324;
- Delphi 6.0 + + update Pack 2 or higher;
- TcAdsDLL.DLL;
- TcAdsDEF.pas and TcAdsAPI.pas, contained in the file delphi_adsdll_api_units.zip, if you want to compile the source code yourself;
The task
15 PLC variables of different types are to be read or written with an ADS sum command.
In Delphi it is possible to pass open array parameters to functions. I.e. the number of array elements in such a function parameter is not fixed. Three new functions have been implemented in the TcAdsApi.pas file: AdsSyncSumReadReq, AdsSyncSumWriteReq and AdsSyncSumReadWriteReq. These functions use the functionality of the open array parameters and simplify the use of the sum command under Delphi. The sample project uses these new wrapper functions.

The PLC program
The PLC variables were declared as global variables. With a started PLC, value changes of the variables are simulated in the simple MAIN program.
VAR_GLOBAL
nBits : BYTE;
wBits : WORD;
dwBits : DWORD;
f32AxPos : REAL;
f64AxPos : LREAL;
bEnable : BOOL;
sValue : STRING := 'INIT';
ui8Num : USINT;
ui16Num : UINT;
ui32Num : UDINT;
i8Num : SINT;
i16Num : INT;
i32Num : DINT;
aui32 : ARRAY[0..9] OF DWORD;
out8Bit AT%QX0.1 : BOOL;
END_VAR
Delphi 6 program
The declarations for the TcAdsDLL functions used are located in the Pascal units TcAdsDEF.pas and TcAdsAPI.pas. They were linked in the project via a Uses clause.
unit Unit1;
interface
uses
TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Grids, ValEdit, ComCtrls, StdCtrls, Math;
The communication port is opened when the program starts and closed when it ends. The required symbol information of the PLC variables such as index group, index offset, etc. is also read once at the start of the program with the aid of a sum command.
/////////////////////////////////////////////////////////////////////////
type
TAdsSymbolInfo = packed record
entry : TAdsSymbolEntry; // ADS symbol info
name : AnsiString; // ADS symbol name
arraySize : Longword;
case typeID : ADS_DATATYPE of //variable part value
ADST_VOID:(); //nothing
ADST_INT16:(vINT : Smallint); //2 byte signed number
ADST_INT32:(vDINT: Longint); //4 byte signed number
ADST_REAL32:(vREAL : Single); //4 byte real
ADST_REAL64:(vLREAL : Double); //8 byte real
ADST_INT8:(vSINT : Shortint); //1 byte signed number
ADST_UINT8:(vBYTE : Byte); //1 byte unsigned number
ADST_UINT16:(vWORD : Word); //2 byte unsigned number
ADST_UINT32:(vDWORD : DWORD); //4 byte unsigned number
ADST_STRING:(vSTRING : Array[0..80] OF AnsiChar); //null terminated string
ADST_BIT:(vBOOL : Boolean); //boolean
ADST_BIGTYPE:(data : Array[0..255] OF Byte); //data byte array
end;
/////////////////////////////////////////////////////////////////////////
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ValueListEditor1: TValueListEditor;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
infos : Array of TAdsSymbolInfo;
client : Longint;
amsAddr : TAmsAddr;
{ Public declarations }
procedure ReadListOfSymbolInfo();
procedure ReadListOfVars();
procedure WriteListOfVars();
procedure ReadListByName();
procedure DataToView( var info : TAdsSymbolInfo);
procedure ViewToData( var info : TAdsSymbolInfo);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
/////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
var adsErr : Longint;
begin
Button1.Enabled := True;
Button2.Enabled := False;
Button3.Enabled := False;
client := AdsPortOpen(); // open ads port
if client <> 0 then
begin
adsErr := AdsGetLocalAddress( @amsAddr );
if adsErr = 0 then
begin
amsAddr.port := AMSPORT_R0_PLC_RTS1; // set port to 801 (first PLC run time system)
ReadListOfSymbolInfo();// read symbol info
end
else
ShowMessageFmt('AdsGetLocalAddress() error: %d [0x%X]', [adsErr, adsErr]);
end
else
ShowMessage('AdsPortOpen() failed!');
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if client <> 0 then
AdsPortClose();// close ads port
end;
Reading the symbol information
/////////////////////////////////////////////////////////////////////////
procedure TForm1.ReadListOfSymbolInfo();
var igList, ioList, writeLenList, readLenList, retLenList : Array of Longword;
writePtrList, readPtrList : Array of Pointer;
resList : Array of Longint;
i, adsErr : Longint;
nInfos : Longword;
begin
nInfos := 15;
SetLength(infos, nInfos);
infos[0].name := '.nBits';
infos[1].name := '.wBits';
infos[2].name := '.dwBits';
infos[3].name := '.f32AxPos';
infos[4].name := '.f64AxPos';
infos[5].name := '.bEnable';
infos[6].name := '.sValue';
infos[7].name := '.ui8Num';
infos[8].name := '.ui16Num';
infos[9].name := '.ui32Num';
infos[10].name := '.i8Num';
infos[11].name := '.i16Num';
infos[12].name := '.i32Num';
infos[13].name := '.aui32';
infos[14].name := '.out8Bit';
SetLength(igList, nInfos);
SetLength(ioList, nInfos);
SetLength(readLenList, nInfos);
SetLength(readPtrList, nInfos);
SetLength(writeLenList, nInfos);
SetLength(writePtrList, nInfos);
SetLength(resList, nInfos);
SetLength(retLenList, nInfos);
for i:=Low(infos) to High(infos) do
begin
igList[i] := ADSIGRP_SYM_INFOBYNAMEEX;
ioList[i] := $0;
readLenList[i] := SizeOf(infos[i].entry);
readPtrList[i] := @infos[i].entry;
writeLenList[i] := Length(infos[i].name) + 1;
writePtrList[i] := @infos[i].name[1];
resList[i] := 0;
retLenList[i] := 0;
end;
adsErr := AdsSyncSumReadWriteReq( @amsAddr, igList, ioList,
readLenList, readPtrList,
writeLenList, writePtrList,
resList,retLenList );
if adsErr = 0 then
begin
for i:=Low(resList) to High(resList) do
begin
if resList[i] <> 0 then
ShowMessageFmt('AdsSyncSumReadWriteReq(reading info "%s") error: %d [0x%X]', [infos[i].name, resList[i], resList[i]]);
end
else
ShowMessageFmt('AdsSyncSumReadWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;
Read values
/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
begin
ValueListEditor1.Strings.Clear();
ReadListOfVars();
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.ReadListOfVars();
var igList, ioList, lenList : Array of Longword;
ptrList : Array of Pointer;
resList : Array of Longint;
i, adsErr : Longint;
begin
SetLength(igList, Length(infos));
SetLength(ioList, Length(infos));
SetLength(lenList, Length(infos));
SetLength(ptrList, Length(infos));
SetLength(resList, Length(infos));
for i:=Low(infos) to High(infos) do
begin
igList[i] := infos[i].entry.iGroup;
ioList[i] := infos[i].entry.iOffs;
lenList[i] := infos[i].entry.size;
ptrList[i] := @infos[i].data[0];
resList[i] := 0;
end;
adsErr := AdsSyncSumReadReq(@amsAddr, igList, ioList, lenList, ptrList, resList );
if adsErr = 0 then
begin
for i:= Low(resList) to High(resList) do
begin
if resList[i] <> 0 then
ShowMessageFmt('AdsSyncSumReadReq(reading value "%s") error: %d [0x%X]', [infos[i].name, resList[i], resList[i]]);
DataToView( infos[i] )// Output read data
end;
end
else
ShowMessageFmt('AdsSyncSumReadReq() error: %d [0x%X]', [adsErr, adsErr]);
Button2.Enabled := adsErr = 0;
Button3.Enabled := adsErr = 0;
end;
Write values
/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button2Click(Sender: TObject);
begin
WriteListOfVars();
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.WriteListOfVars();
var igList, ioList, lenList : Array of Longword;
srcList : Array of Pointer;
resList : Array of Longint;
i, adsErr : Longint;
begin
SetLength(igList, Length(infos));
SetLength(ioList, Length(infos));
SetLength(lenList, Length(infos));
SetLength(srcList, Length(infos));
SetLength(resList, Length(infos));
for i:= Low(infos) to High(infos) do
begin
try
ViewToData( infos[i] ); // refresh value
finally
igList[i] := infos[i].entry.iGroup;
ioList[i] := infos[i].entry.iOffs;
lenList[i] := infos[i].entry.size;
srcList[i] := @infos[i].data[0];
resList[i] := 0;
end;
end;
adsErr := AdsSyncSumWriteReq( @amsAddr,
igList,
ioList,
lenList,
srcList,
resList );
if adsErr = 0 then
begin
for i:= Low(resList) to High(resList) do
begin
if resList[i] <> 0 then
ShowMessageFmt('AdsSyncSumWriteReq(writing value "%s") error: %d [0x%X]', [infos[i].name, resList[i], resList[i]]);
end;
end
else
ShowMessageFmt('AdsSyncSumWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;
Read values "by name"
/////////////////////////////////////////////////////////////////////////
procedure TForm1.Button3Click(Sender: TObject);
begin
ValueListEditor1.Strings.Clear();
ReadListByName();
end;
/////////////////////////////////////////////////////////////////////////
procedure TForm1.ReadListByName();
var igList, ioList, writeLenList, readLenList, retLenList : Array of Longword;
writePtrList, readPtrList : Array of Pointer;
resList : Array of Longint;
i, adsErr : Longint;
begin
SetLength(igList, Length(infos));
SetLength(ioList, Length(infos));
SetLength(readLenList, Length(infos));
SetLength(readPtrList, Length(infos));
SetLength(writeLenList, Length(infos));
SetLength(writePtrList, Length(infos));
SetLength(resList, Length(infos));
SetLength(retLenList, Length(infos));
for i:=Low(infos) to High(infos) do
begin
igList[i] := ADSIGRP_SYM_VALBYNAME;
ioList[i] := $0;
readLenList[i] := SizeOf(infos[i].data);
readPtrList[i] := @infos[i].data[0];
writeLenList[i] := Length(infos[i].name) + 1;
writePtrList[i] := @infos[i].name[1];
resList[i] := 0;
retLenList[i] := 0;
end;
adsErr := AdsSyncSumReadWriteReq( @amsAddr, igList, ioList,
readLenList, readPtrList,
writeLenList, writePtrList,
resList,retLenList );
if adsErr = 0 then
begin
for i:=Low(resList) to High(resList) do
begin
if resList[i] <> 0 then
ShowMessageFmt('AdsSyncSumReadWriteReq(reading value "%s) error:%d [0x%X]', [infos[i].name, resList[i], resList[i]]);
DataToView( infos[i] )// Output read data
end;
end
else
ShowMessageFmt('AdsSyncSumReadWriteReq() error: %d [0x%X]', [adsErr, adsErr]);
end;
Simple procedures to display read values in the dialog or to convert the values to be written from the dialog into data to be transferred.
/////////////////////////////////////////////////////////////////////////
procedure TForm1.DataToView( var info : TAdsSymbolInfo );
var sValue : String;
index : integer;
list : TStringList;
bPickList : Boolean;
begin
list := TStringList.Create();
bPickList := False;
sValue := '';
info.arraySize := 0;
info.typeID := ADS_DATATYPE(info.entry.dataType);
case info.typeID of
ADST_INT8:
begin
info.arraySize := info.entry.size Div sizeof(info.vSINT);
sValue := Format('%d', [info.vSINT]);
end;
ADST_INT16:
begin
info.arraySize := info.entry.size Div sizeof(info.vINT);
sValue := Format('%d', [info.vINT]);
end;
ADST_INT32:
begin
info.arraySize := info.entry.size Div sizeof(info.vDINT);
sValue := Format('%d', [info.vDINT]);
end;
ADST_UINT8:
begin
info.arraySize := info.entry.size Div sizeof(info.vBYTE);
sValue := Format('%u', [info.vBYTE]);
end;
ADST_UINT16:
begin
info.arraySize := info.entry.size Div sizeof(info.vWORD);
sValue := Format('%u', [info.vWORD]);
end;
ADST_UINT32:
begin
info.arraySize := info.entry.size Div sizeof(info.vDWORD);
sValue := Format('%u', [info.vDWORD]);
end;
ADST_BIT:
begin
info.arraySize := info.entry.size Div sizeof(info.vBOOL);
sValue := Format('%s', [BoolToStr(info.vBOOL, true)]);
list.Add('True');
list.Add('False');
bPickList := True;
end;
ADST_STRING:
begin
info.arraySize := info.entry.size Div sizeof(info.vSTRING);
sValue := Format('%s', [Trim(String(info.vSTRING))]); //Trim = Cut characters behind of string end delimiter
end;
ADST_REAL32:
begin
info.arraySize := info.entry.size Div sizeof(info.vREAL);
sValue := Format('%f', [info.vREAL]);
end;
ADST_REAL64:
begin
info.arraySize := info.entry.size Div sizeof(info.vLREAL);
sValue := Format('%f', [info.vLREAL]);
end;
ADST_BIGTYPE:
sValue := 'BIGTYPE';
else
sValue := 'Unknown type?';
end;
index := ValueListEditor1.Strings.Add(Format('%s=%s', [info.name, sValue]));
if bPickList then
begin
ValueListEditor1.itemProps[index].EditStyle := esPickList;
ValueListEditor1.itemProps[index].PickList.AddStrings(list);
end;
list.Destroy();
end;
//////////////////////////////////////////////////////////////////////////////
procedure TForm1.ViewToData( var info : TAdsSymbolInfo);
var sValue : String;
begin
try
sValue := ValueListEditor1.Values[String(info.name)];
case info.typeID of
ADST_INT8:
begin
info.vSINT := Shortint(StrToInt(sValue));
end;
ADST_INT16:
begin
info.vINT := Smallint(StrToInt(sValue));
end;
ADST_INT32:
begin
info.vDINT := Longint(StrToInt64(sValue));
end;
ADST_UINT8:
begin
info.vBYTE := Byte(StrToInt(sValue));
end;
ADST_UINT16:
begin
info.vWORD := Word(StrToInt(sValue));
end;
ADST_UINT32:
begin
info.vDWORD := Longword(StrToInt64(sValue));
end;
ADST_BIT:
begin
info.vBOOL := StrToBool(sValue);
end;
ADST_STRING:
begin
StrPCopy(@info.vSTRING[0], AnsiString(sValue));
end;
ADST_REAL32:
begin
info.vREAL:= StrToFloat(sValue);
end;
ADST_REAL64:
begin
info.vLREAL:= StrToFloat(sValue);
end;
ADST_BIGTYPE:
;
else
;
end;
except
on E: EConvertError do
ShowMessage(E.ClassName + E.Message);
end;
end;
end.
Language / IDE | Unpack sample program |
---|---|
Delphi XE2 | |
Delphi 6 or higher (classic) |