ADS sum command: read and write multiple variables with an ADS command.

Requirements:

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.

ADS sum command: read and write multiple variables with an ADS command. 1:

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

delphixe2_api_ADS-DLL Sample07.exe

Delphi 6 or higher (classic)

Sample07.exe