Notes on the documentation

This description is only intended for the use of trained specialists in control and automation engineering who are familiar with the applicable national standards.
It is essential that the following notes and explanations are followed when installing and commissioning these components.

The responsible staff must ensure that the application or use of the products described satisfy all the requirements for safety, including all the relevant laws, regulations, guidelines and standards.

Disclaimer

The documentation has been prepared with care. The products described are, however, constantly under development.
For that reason the documentation is not in every case checked for consistency with performance data, standards or other characteristics.
In the event that it contains technical or editorial errors, we retain the right to make alterations at any time and without warning.
No claims for the modification of products that have already been supplied may be made on the basis of the data, diagrams and descriptions in this documentation.

Trademarks

Beckhoff®, TwinCAT®, EtherCAT®, Safety over EtherCAT®, TwinSAFE® and XFC® are registered trademarks of and licensed by Beckhoff Automation GmbH.
Other designations used in this publication may be trademarks whose use by third parties for their own purposes could violate the rights of the owners.

Patent Pending

The TwinCAT Technology is covered, including but not limited to the following patent applications and patents:
EP0851348, US6167425 with corresponding applications or registrations in various other countries.

Copyright

© Beckhoff Automation GmbH & Co. KG, Germany.
The reproduction, distribution and utilization of this document as well as the communication of its contents to others without express authorization are prohibited.
Offenders will be held liable for the payment of damages. All rights reserved in the event of the grant of a patent, utility model or design.


VB TcAds.NET sample overview

TwinCAT ADS Samples - Visual Basic

Overview

Integration in Visual Basic

Description
Length of Visual Basic 6.0 variables

TwinCAT ADS-OCX

Description
Linking of OCX into Visual Basic 6.0
Installation of ADS-OCX
Description | Source code
Sample 1: Accessing an array in the PLC | ADS-OCX Sample01.exe
Sample 2: Transmitting structures to the PLC | ADS-OCX Sample02.exe
Sample 3: Event driven reading | ADS-OCX Sample03.exe
Sample 4: Read PLC variable declaration | ADS-OCX Sample04.exe
Sample 5: Detect/alter status in the TwinCAT Router and the PLC | ADS-OCX Sample05.exe
Sample 6: Send/receive messages via the TwinCAT Router | ADS-OCX Sample06.exe
Sample 7: Delete a handle of a PLC variable | ADS-OCX Sample07.exe
Sample 9: Event driven reading (with converting notification data) | ADS-OCX Sample09.exe

TwinCAT ADS-DLL

Description | Source code
Sample 1: Reading and Writing of PLC Variables | TcAdsDll_ComSample01.exe
Sample 2: Event driven reading | TcAdsDll_ComSample02.exe

TwinCAT ADS.NET

Description
Linking in Microsoft Visual Studio .NET (VB)
Description | Source code (for .NET framework)
Sample 1: Accessing an array in the PLC | TwinCATADS_VBSample01.exe
Sample 2: Transmitting a structure to the PLC | TwinCATADS_VBSample02.exe
Sample 3: Event driven reading | TwinCATADS_VBSample03.exe
Sample 4: Reading and writing of STRING variables | TwinCATADS_VBSample04.exe TwinCAT.Ads.NET v1.0.0.10 and higher: TwinCATADS_VBSample04b.exe
Sample 5: Reading and writing of DATE/TIME variables | TwinCATADS_VBSample05.exe
Sample 6: Read PLC variable declaration | TwinCATADS_VBSample06.exe
Sample 7: Reading and writing of PLC variables of any type (ReadAny, WriteAny) | TwinCATADS_VBSample07.exe

TwinCAT ADS-Script-DLL

Description | Source code
Sample 2: Reading and Writing of PLC Variables | ADS-Script-DLL Sample02.exe


ADS Return Codes

Error codes: 0x000..., 0x500..., 0x700..., 0x1000..., 0x274C...

Global Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x0 | 0 | no error |  |
0x1 | 1 | Internal error |  |
0x2 | 2 | No Rtime |  |
0x3 | 3 | Allocation locked memory error |  |
0x4 | 4 | Insert mailbox error | No ADS mailbox was available to process this message. | Reduce the number of ADS calls (e.g ADS-Sum commands or Max Delay Parameter)
0x5 | 5 | Wrong receive HMSG |  |
0x6 | 6 | target port not found | ADS Server not started |
0x7 | 7 | target machine not found | Missing ADS routes |
0x8 | 8 | Unknown command ID |  |
0x9 | 9 | Bad task ID |  |
0xA | 10 | No IO |  |
0xB | 11 | Unknown ADS command |  |
0xC | 12 | Win 32 error |  |
0xD | 13 | Port not connected |  |
0xE | 14 | Invalid ADS length |  |
0xF | 15 | Invalid ADS Net ID |  |
0x10 | 16 | Low Installation level |  |
0x11 | 17 | No debug available |  |
0x12 | 18 | Port disabled |  |
0x13 | 19 | Port already connected |  |
0x14 | 20 | ADS Sync Win32 error |  |
0x15 | 21 | ADS Sync Timeout |  |
0x16 | 22 | ADS Sync AMS error |  |
0x17 | 23 | ADS Sync no index map |  |
0x18 | 24 | Invalid ADS port |  |
0x19 | 25 | No memory |  |
0x1A | 26 | TCP send error |  |
0x1B | 27 | Host unreachable |  |
0x1C | 28 | Invalid AMS fragment |  |

Router Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x500 | 1280 | ROUTERERR_NOLOCKEDMEMORY | No locked memory can be allocated |
0x501 | 1281 | ROUTERERR_RESIZEMEMORY | The size of the router memory could not be changed |
0x502 | 1282 | ROUTERERR_MAILBOXFULL | The mailbox has reached the maximum number of possible messages. The current sent message was rejected | Check the connection between the communication partners
0x503 | 1283 | ROUTERERR_DEBUGBOXFULL | The mailbox has reached the maximum number of possible messages. The sent message will not be displayed in the debug monitor | Check the connection to the debug monitor
0x504 | 1284 | ROUTERERR_UNKNOWNPORTTYPE | The port type is unknown |
0x505 | 1285 | ROUTERERR_NOTINITIALIZED | Router is not initialised |
0x506 | 1286 | ROUTERERR_PORTALREADYINUSE | The desired port number is already assigned |
0x507 | 1287 | ROUTERERR_NOTREGISTERED | Port not registered |
0x508 | 1288 | ROUTERERR_NOMOREQUEUES | The maximum number of Ports reached |
0x509 | 1289 | ROUTERERR_INVALIDPORT | The port is invalid. |
0x50A | 1290 | ROUTERERR_NOTACTIVATED | TwinCAT Router not active |
0x50B | 1291 | ROUTERERR_FRAGMENTBOXFULL |  |
0x50C | 1292 | ROUTERERR_FRAGMENTTIMEOUT |  |
0x50D | 1293 | ROUTERERR_TOBEREMOVED |  |

General ADS Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x700 | 1792 | error class <device error> |  |
0x701 | 1793 | Service is not supported by server |  |
0x702 | 1794 | invalid index group |  |
0x703 | 1795 | invalid index offset |  |
0x704 | 1796 | reading/writing not permitted |  |
0x705 | 1797 | parameter size not correct |  |
0x706 | 1798 | invalid parameter value(s) |  |
0x707 | 1799 | device is not in a ready state |  |
0x708 | 1800 | device is busy |  |
0x709 | 1801 | invalid context (must be in Windows) |  |
0x70A | 1802 | out of memory |  |
0x70B | 1803 | invalid parameter value(s) |  |
0x70C | 1804 | not found (files, ...) |  |
0x70D | 1805 | syntax error in command or file |  |
0x70E | 1806 | objects do not match |  |
0x70F | 1807 | object already exists |  |
0x710 | 1808 | symbol not found |  |
0x711 | 1809 | symbol version invalid | Onlinechange | Release handle and get a new one
0x712 | 1810 | server is in invalid state |  |
0x713 | 1811 | AdsTransMode not supported |  |
0x714 | 1812 | Notification handle is invalid | Onlinechange | Release handle and get a new one
0x715 | 1813 | Notification client not registered |  |
0x716 | 1814 | no more notification handles |  |
0x717 | 1815 | size for watch too big |  |
0x718 | 1816 | device not initialized |  |
0x719 | 1817 | device has a timeout |  |
0x71A | 1818 | query interface failed |  |
0x71B | 1819 | wrong interface required |  |
0x71C | 1820 | class ID is invalid |  |
0x71D | 1821 | object ID is invalid |  |
0x71E | 1822 | request is pending |  |
0x71F | 1823 | request is aborted |  |
0x720 | 1824 | signal warning |  |
0x721 | 1825 | invalid array index |  |
0x722 | 1826 | symbol not active | Onlinechange | Release handle and get a new one
0x723 | 1827 | access denied |  |
0x724 | 1828 | missing license |  | Activate license for TwinCAT 3 function
0x72c | 1836 | exception occured during system start |  | Check each device transistions
0x740 | 1856 | Error class <client error> |  |
0x741 | 1857 | invalid parameter at service |  |
0x742 | 1858 | polling list is empty |  |
0x743 | 1859 | var connection already in use |  |
0x744 | 1860 | invoke ID in use |  |
0x745 | 1861 | timeout elapsed |  | Check ADS routes of sender and receiver and your firewall setting
0x746 | 1862 | error in win32 subsystem |  |
0x747 | 1863 | Invalid client timeout value |  |
0x748 | 1864 | ads-port not opened |  |
0x750 | 1872 | internal error in ads sync |  |
0x751 | 1873 | hash table overflow |  |
0x752 | 1874 | key not found in hash |  |
0x753 | 1875 | no more symbols in cache |  |
0x754 | 1876 | invalid response received |  |
0x755 | 1877 | sync port is locked |  |

RTime Error Codes

Hex | Dec | Description | Possible Causes
0x1000 | 4096 | RTERR_INTERNAL | Internal fatal error in the TwinCAT real-time system
0x1001 | 4097 | RTERR_BADTIMERPERIODS | Timer value not vaild
0x1002 | 4098 | RTERR_INVALIDTASKPTR | Task pointer has the invalid value ZERO
0x1003 | 4099 | RTERR_INVALIDSTACKPTR | Task stack pointer has the invalid value ZERO
0x1004 | 4100 | RTERR_PRIOEXISTS | The demand task priority is already assigned
0x1005 | 4101 | RTERR_NOMORETCB | No more free TCB (Task Control Block) available. Maximum number of TCBs is 64
0x1006 | 4102 | RTERR_NOMORESEMAS | No more free semaphores available. Maximum number of semaphores is 64
0x1007 | 4103 | RTERR_NOMOREQUEUES | No more free queue available. Maximum number of queue is 64
0x1008 | 4104 | TwinCAT reserved. |
0x1009 | 4105 | TwinCAT reserved. |
0x100A | 4106 | TwinCAT reserved. |
0x100B | 4107 | TwinCAT reserved. |
0x100C | 4108 | TwinCAT reserved. |
0x100D | 4109 | RTERR_EXTIRQALREADYDEF | An external synchronisation interrupt is already applied
0x100E | 4110 | RTERR_EXTIRQNOTDEF | No external synchronsiation interrupt applied
0x100F | 4111 | RTERR_EXTIRQINSTALLFAILED | The apply of the external synchronisation interrupt failed
0x1010 | 4112 | RTERR_IRQLNOTLESSOREQUAL | Call of a service function in the wrong context
0x1017 | 4119 | RTERR_VMXNOTSUPPORTED | Intel VT-x extension is not supported.
0x1018 | 4120 | RTERR_VMXDISABLED | Intel VT-x extension is not enabled in BIOS.
0x1019 | 4121 | RTERR_VMXCONTROLSMISSING | Missing feature in Intel VT-x extension.
0x101A | 4122 | RTERR_VMXENABLEFAILS | Enabling Intel VT-x fails.

TCP Winsock Error Codes

Hex | Dec | Description | Possible Causes | Solution
0x274c | 10060 | A socket operation was attempted to an unreachable host | Host unreachable | Check network connection via ping
0x274d | 10061 | A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. | Host unreachable | Check network connection via ping
0x2751 | 10065 | No connection could be made because the target machine actively refused it |  |
 |  | Further Winsock error codes: Win32 Error Codes |  |


Visual Basic 6.0 variable length

TwinCAT ADS-VB-Variablelength

Length of Visual Basic 6.0 variables

VB-Variable type | Variablelength in byte
Boolean | 2
Integer | 2
Long | 4
Single | 4
Double | 8
String | Number of character * 2
Byte | 1

Length of arrays

The byte-length of an array is calculated by the number of elements in the array, multiplied with the length of the variable type.

Sample

For reading or writing an array of the typ long, the length is 20 byte(5 elements * 4 byte).

The length of a string with 25 character is 50 byte(25 character * 2 byte).


Linking into Visual Basic

Select the ADS-OCX

In order to select the ADS-OCX you must choose the command Components... under the Project menu item in Visual Basic, and mark the AdsOcx OLE Control module entry.

The ADS-OCX then appears in the Visual Basic toolbox (bottom right).

Define properties

Before you can use the ADS-OCX you must drag it onto a form and adjust the properties. The general properties can be configured or read using either the properties pages of the ADS-OCX or the Visual Basic properties list. The representation displayed on the properties page is particularly clear, since the properties are sorted here into groups. In order to make the Visual Basic program more readable, the names of (almost) all of the properties, methods and events begin with Ads.

AMS Properties

This page describes the communication channel between the ADS-OCX and the ADS device that is to be addressed. The terms ADS client and ADS server are also used here. The ADS client is the program that requests information or services from the ADS server. In our case, the Visual Basic program with the ADS-OCX is the ADS client. In most of the examples the TwinCAT PLC server is the ADS server.

Each ADS device within TwinCAT has a unique address. This address is composed of a NetId and the port number. The NetId must be unique for each TwinCAT system within a network. The NetId of a computer can be read through the system control in the TwinCAT system properties dialogue on the AMS router page. The NetId consists of 6 digits, separated from one another by a point. Do not provide a NetId when you want to address local ADS devices. In addition to the NetId, each ADS device is also addressed by a port number. Each port number may only exist once on a TwinCAT computer. Further information is to be found under TwinCAT ADS.

If, for instance, you want to address run-time system 1 in the PLC in the local computer, you should leave the server's NetId field empty, and enter port number 801 as the server port. The fields under Client can have different values in your case from those illustrated above.

If you want to address a number of ADS devices with the ADS-OCX (e.g. PLC and NC), you should use an ADS-OCX for each of these ADS devices. You should not alter the port number of the NetId while the system is running.

ADS Client Properties

This page shows the type and version of the ADS device with the ADS-OCX, as well as the current status of the ADS interface within the ADS-OCX.

The Identification properties group shows the type, version, revision and build of the ADS-OCX. The other communication partner is able to interrogate these values in order to obtain more information about the ADS device. These fields can be freely set by the application.
The Control/State properties group shows the current status of the ADS interface within the ADS-OCX.

ADS Server Properties

This page shows the type and version of the ADS device that is to be addressed by the ADS-OCX. The current status of the ADS device is also displayed.

The Identification properties group shows the type, version, revision and build of the ADS device that is addressed by the ADS-OCX. These properties are read-only.
The Control/State properties group shows the current status of the ADS interface within the ADS device.
The Refresh properties group is reserved, and is not yet supported.

The PLC is addressed by the ADS-OCX in the dialogue shown above. This is in the RUN state, and is of version 2.6.0.

The ADS-OCX is now configured in such a way that it can address the PLC.


AdsOcx Visual Basic Example: Installing the ADS OCX

ADS-OCX: Setup

Installing the ADS OCX

The ADS OCX can be installed with Regsvr32. The path to the file that is to be registered must be stated.

Open the Run shell (Start -->Run...); type Regsvr32 <path to adsocx.ocx>\adsocx.ocx; Enter

It is not necessary to reboot the computer.


AdsOcx Visual Basic Example01: Accessing an array in the PLC

TwinCAT ADS-OCX: Examples

Accessing an array in the PLC

Task

The PLC contains an array that is to be read by Visual Basic using a read command.

Description

The PLC contains an array of 100 elements of type integer (2 bytes). The array in the PLC is to be filled with the values from 3500 to 3599.
The first step in the load event function of the Visual Basic program is to fetch the handle of the PLC variable. When the program finishes, this will be released again in the unload event function.
When the user clicks the button on the form, the entire array is read from the PLC into the Visual Basic variable Data by means of the AdsSyncRead[Datatype]VarReq() method.
The variable Data must have the same structure as the corresponding variable in the PLC; 100 elements of type integer (2 bytes). The length stated when the method is called is 200, since the length of the requested data is 200 bytes (100 elements of 2 bytes each).
In the following FOR loop, the array from the PLC is displayed in a listbox control.

Visual Basic 6 program

Dim hVar As Long
Dim Data(100) As Integer

'--- Is called at the start ---
Private Sub Form_Load()
  '--- Exception freigeben --- AdsOcx1.EnableErrorHandling = True
  Call AdsOcx1.AdsCreateVarHandle("Main.PLCVar", hVar)
End Sub

'--- Is called at the end ---
Private Sub Form_Unload(Cancel As Integer)
  Call AdsOcx1.AdsDeleteVarHandle(hVar)
End Sub

'--- Is called by the user ---
Private Sub cmd_read_Click()
  Dim intIndex As Integer
  '--- Read complete array ---
  Call AdsOcx1.AdsSyncReadIntegerVarReq(hVar, 200, Data(0))
  '--- Display array elements in form ---
  lstArray.Clear
  For intIndex = 0 To 99
    lstArray.AddItem (CStr(intIndex) & Chr(vbKeyTab) & _ CStr(Data(intIndex)))
  Next
End Sub

PLC program

PROGRAM MAIN
VAR
  PLCVar : ARRAY [0..99] OF INT;
  Index: BYTE;
END_VAR

FOR Index := 0 TO 99 DO
  PLCVar[Index] := 3500 + INDEX;
END_FOR

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample01.exe


AdsOcx Visual Basic Example02: Transmitting structures to the PLC

TwinCAT ADS-OCX: Examples

Transmitting structures to the PLC

Task

A structure is to be written into the PLC by Visual Basic. The elements in the structure have different data types.

Description

In order for the CPU running under Windows NT/2000 to be able to access the variables more quickly, Visual Basic (as well as other programming languages) arranges them in the main memory according to certain rules. This arrangement of variables is called alignment. This can mean that 'memory holes' occur within a structure. Since Visual Basic and IEC1131-3 have different alignment rules, they must be filled by dummy variables.
Unfortunately a general rule for alignment under Visual Basic cannot be defined. There are however two Visual Basic functions which allow the memory assignments of a structure to be analysed. These are the VarPtr() and LenB() functions.
VarPtr() returns the address of a variable, while LenB() returns the length (in bytes) occupied by a variable (or by a whole structure). The example below illustrates the memory map of the structure in a form. This information can be used to determine where the structure has 'memory gaps'. These are filled in the example program by the VarDummyX variable.
Warning: The VarPtr() function is only available as from Visual Basic 5.

The following sketch shows the memory allocation graphically once more:

A rectangle means that the variable occupies one byte at this location. A cross represents the location of a byte that is not used by any variables. In the example program the crosses are filled by dummy variables.

Structure in the Visual Basic program

Type VBStruct
     VarInteger   As Integer
    VarDummy1    As Integer
    VarLong      As Long
    VarByte      As Byte
    VarDummy2    As Byte
    VarDummy3    As Byte
    VarDummy4    As Byte
    VarDouble    As Double
    VarSingle    As Single
End Type

Structure in the PLC program

After the alignment of the structure in the Visual Basic program has been adjusted, the structure in the PLC program must also be extended:

TYPE PLCStruct
STRUCT
    PLCVarInteger : INT;
    PLCVarDummy1  : INT;
    PLCVarLong    : DINT;
    PLCVarByte    : SINT;
    PLCVarDummy2  : SINT;
    PLCVarDummy3  : SINT;
    PLCVarDummy4  : SINT;
    PLCVarDouble  : LREAL;
    PLCVarSingle  : REAL;
END_STRUCT
END_TYPE

Visual Basic 6 program

Dim hVar As Long
Dim VBVar As VBStruct

'--- Is called at the start ---
Private Sub Form_Load()
     '--- Enable exception --- AdsOcx1.EnableErrorHandling = True
    Call AdsOcx1.AdsCreateVarHandle("Main.PLCVar", hVar)
    '--- Display addresses of the variables ---
    lblInteger.Caption = VarPtr(VBVar.VarInteger)
    lblLong.Caption = VarPtr(VBVar.VarLong)
    lblByte.Caption = VarPtr(VBVar.VarByte)
    lblDouble.Caption = VarPtr(VBVar.VarDouble)
    lblSingle.Caption = VarPtr(VBVar.VarSingle)
    '--- Display length of the structure ---
    lblVarLength.Caption = LenB(VBVar)
End Sub

'--- Is called at the end ---
Private Sub Form_Unload(Cancel As Integer)
    Call AdsOcx1.AdsDeleteVarHandle(hVar)
End Sub

'--- Is called by the user ---
Private Sub cmd_write_Click()
    Dim intIndex As Integer
    '--- Fill structure ---
    VBVar.VarInteger = CInt(txtInteger.Text)
    VBVar.VarLong = CLng(txtLong.Text)
    VBVar.VarByte = CByte(txtByte.Text)
    VBVar.VarDouble = CDbl(txtDouble.Text)
    VBVar.VarSingle = CSng(txtSingle.Text)
    '--- Write structure to PLC ---
    Call AdsOcx1.AdsSyncWriteIntegerVarReq(hVar, LenB(VBVar), VBVar.VarInteger)
End Sub

PLC program

PROGRAM MAIN
VAR
  PLCVar : PLCStruct;
END_VAR

Optimizations

A clever arrangement of the VBStruct member variables in the VB application can avoid the adding of dummy bytes. Please regard the following rules:

  • Arrange the member variables in the VB structure according to the occupied memory size: The biggest data types first.
  • The last bytes can be refilled (but needn't) to full 4 bytes.

Optimised structure declaration in Visual Basic

Type VBStruct
    VarDouble As Double 	' 8 bytes
    VarSingle As Single	'+4 bytes
    VarLong As Long		'+4 byte
    VarInteger As Integer	'+2 bytes
    VarByte As Byte		'+1 byte
                '+1 hidden padding byte in memory
                '=20 bytes (LenB result)
End Type

Optimised structure declaration in the PLC

TYPE PLCStruct
STRUCT
    PLCVarDouble : LREAL;
    PLCVarSingle : REAL;
    PLCVarLong : DINT;
    PLCVarInteger : INT;
    PLCVarByte : SINT;
END_STRUCT
END_TYPE

The optimised VB structure begins with Double, accordant the VB program has to be changed:

'--- Is called by the user ---
Private Sub cmd_write_Click()
...
    '--- Write structure to PLC ---
    call AdsOcx1.AdsSyncWriteDoubleVarReq(hVar, Len(VBVar), VBVar.VarDouble)
End Sub

Among the changed method name the data length to be written has to be aquired with the Len function, not with LenB. If you use LenB , the data are not written to the PLC, because LenB returns a length = 20 bytes (padding bytes in VB memory included), and our structure in the PLC has a length of 19 bytes.

Len vs. LenB

  • With user-defined types, Len returns the size as it will be written to the file.
  • With user-defined types, LenB returns the in-memory size, including any padding between elements.

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample02.exe


AdsOcx Visual Basic Example03: Event driven reading

TwinCAT ADS-OCX: Examples

Event driven reading

Task

There are 7 global variables in the PLC. Each of these PLC variables is of a different data type. The values of the variables should be read in the most effective manner, and the value with its timestamp is to be displayed on a form in Visual Basic.

Description

In the form's load event, a connection to each of the PLC variables is created with the AdsReadVarConnectEx() method. The handle for this connection is stored in a global array.
The second parameter passed to the AdsReadVarConnectEx() method indicates the type of data exchange. ADSTRANS_SERVERONCHA has been selected here. This means that the value of the PLC variable is only transmitted if its value within the PLC has changed (see the ADSOCXTRANSMODE data type). The third parameter indicates that the PLC is to check whether the corresponding variable has changed every 100 ms.

When the PLC variable changes, the AdsReadConnectUpdateEx() event is called. The time stamp, the handle, the value and a reference to the control in which the value is to be displayed are passed as parameters.
The connections are released again in the unload event by means of the AdsDisconnectEx() method. It is essential that you do this, since every connection established by AdsReadVarConnectEx() uses resources.
You should also choose appropriate values for the cycle time, since too many write / read operations load the system so heavily that the user interface becomes much slower.

Visual Basic 6 program

Option Explicit

Dim hConnect(0 to 6) As Long

Private Sub Form_Load()
    Dim nErr As Long

    nErr = AdsOcx1.AdsReadVarConnectEx(".PLCVarBoolean", ADSTRANS_SERVERONCHA, 100, hConnect(0), lblBoolean)
    If (nErr > 0) Then Call MsgBox("Error AdsReadVarConnectEx -> .PLCVarBoolean: " & nErr)

    nErr = AdsOcx1.AdsReadVarConnectEx(".PLCVarInteger", ADSTRANS_SERVERONCHA, 100, hConnect(1), lblInteger)
    If (nErr > 0) Then Call MsgBox("Error AdsReadVarConnectEx -> .PLCVarInteger: " & nErr)

    nErr = AdsOcx1.AdsReadVarConnectEx(".PLCVarLong", ADSTRANS_SERVERONCHA, 100, hConnect(2), lblLong)
    If (nErr > 0) Then Call MsgBox("Error AdsReadVarConnectEx -> .PLCVarLong: " & nErr)

    nErr = AdsOcx1.AdsReadVarConnectEx(".PLCVarDouble", ADSTRANS_SERVERONCHA, 100, hConnect(3), lblDouble)
    If (nErr > 0) Then Call MsgBox("Error AdsReadVarConnectEx -> .PLCVarDouble: " & nErr)

    nErr = AdsOcx1.AdsReadVarConnectEx(".PLCVarSingle", ADSTRANS_SERVERONCHA, 100, hConnect(4), lblSingle)
    If (nErr > 0) Then Call MsgBox("Error AdsReadVarConnectEx -> .PLCVarSingle: " & nErr)

    nErr = AdsOcx1.AdsReadVarConnectEx(".PLCVarString", ADSTRANS_SERVERONCHA, 100, hConnect(5), lblString)
    If (nErr > 0) Then Call MsgBox("Error AdsReadVarConnectEx -> .PLCVarString: " & nErr)

   nErr = AdsOcx1.AdsReadVarConnectEx(".PLCVarByte", ADSTRANS_SERVERONCHA, 100, hConnect(6), lblByte)
    If (nErr > 0) Then Call MsgBox("Error AdsReadVarConnectEx -> .PLCVarByte: " & nErr)
End Sub

Private Sub AdsOcx1_AdsReadConnectUpdateEx(ByVal dateTime As Date,
                                           ByVal nMs As Long,
                                           ByVal hConnect As Long,
                                           ByVal data As Variant,
                                           Optional ByVal hUser As Variant)
    hUser.Caption = ("Date Time: " & dateTime & "," & nMs & "  Data: " & data)
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Dim nIndex As Long
    For nIndex = 0 To 6
        Call AdsOcx1.AdsDisconnectEx(hConnect(nIndex))
    Next
End Sub

PLC program

VAR_GLOBAL
    PLCVarBoolean	: BOOL;
    PLCVarInteger	: INT;
    PLCVarLong	: DINT;
    PLCVarDouble	: LREAL;
    PLCVarSingle	: REAL;
    PLCVarString	: STRING(10);
    PLCVarByte	: BYTE;
 END_VAR

PROGRAM MAIN
VAR
    ;
END_VAR

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample03.exe


AdsOcx Visual Basic Example04: Read PLC variable declaration

TwinCAT ADS-OCX: Examples

Read PLC variable declaration

Task

All the information about the variables is to be read from the PLC (symbol upload).

Description

The cmdReadSymbols_Click() event function is called by clicking the button on the form. The AdsReadSymbolInfo() method supplies the number of variables (symbols) and the length of the data in which the symbols are stored. The parameter bNext must be set to FALSE the first time the AdsEnumSymbols() method is called. This results in all the information about the first symbol being read. For every subsequent call, bNext is set to TRUE. In the FOR loop, AdsEnumSymbols() is called as many times as there are symbols in the PLC.

Visual Basic 6 program

Option Explicit

'--- Is called at the start of the program ---
Private Sub Form_Load()
    '--- Enable exception ---
    AdsOcx1.EnableErrorHandling = True
    '--- Clear display list and boxes ---
    lstSymbols.Clear
    lblSymbols.Caption = "Symbols: "
End Sub

'--- Is called by the user ---
Private Sub cmdReadSymbols_Click()
    Dim nSymbolsAvailable As Long
    Dim cbBufSizeNeeded As Long
    Dim strSymbolName As String
    Dim strComment As String
    Dim nSymbolType As Long
    Dim cbSymbolSize As Long
    Dim nIndexOffset As Long
    Dim nIndexGroup As Long
    Dim intIndex As Long

    '--- Clear display list ---
    lstSymbols.Clear

    Call AdsOcx1.AdsReadSymbolInfo(nSymbolsAvailable, cbBufSizeNeeded)
    lblSymbols.Caption = "Symbols: " & nSymbolsAvailable

    '--- Load and display the first symbol ---
    Call AdsOcx1.AdsEnumSymbols(strSymbolName, nSymbolType, cbSymbolSize, _
                                strComment, nIndexGroup, nIndexOffset, False)
    lstSymbols.AddItem ("0" & vbTab & Format(strSymbolName, "!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") & vbTab & _
                        GetSymbolType(nSymbolType) & vbTab & cbSymbolSize & vbTab & strComment & vbTab & _
                        "0x" & Hex(nIndexGroup) & vbTab & "0x" & Hex(nIndexOffset))

    '--- Load and display the remaining symbols ---
    For intIndex = 1 To nSymbolsAvailable - 1
        Call AdsOcx1.AdsEnumSymbols(strSymbolName, nSymbolType, cbSymbolSize, _
                                    strComment, nIndexGroup, nIndexOffset, True)
        lstSymbols.AddItem (intIndex & vbTab & Format(strSymbolName, "!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") & vbTab & _
                            GetSymbolType(nSymbolType) & vbTab & cbSymbolSize & vbTab & strComment & vbTab & _
                            "0x" & Hex(nIndexGroup) & vbTab & "0x" & Hex(nIndexOffset))
    Next intIndex
End Sub

Private Function GetSymbolType(VarType As ADSDATATYPEID) As String
    Select Case VarType
        Case ADST_BIT:     GetSymbolType = "BIT"
        Case ADST_INT8:    GetSymbolType = "INT8"
        Case ADST_INT16:   GetSymbolType = "INT16"
        Case ADST_INT32:   GetSymbolType = "INT32"
        Case ADST_INT64:   GetSymbolType = "INT64"
        Case ADST_UINT8:   GetSymbolType = "UINT8"
        Case ADST_UINT16:  GetSymbolType = "UINT16"
        Case ADST_UINT32:  GetSymbolType = "UINT32"
        Case ADST_UINT64:  GetSymbolType = "UINT64"
        Case ADST_REAL32:  GetSymbolType = "REAL32"
        Case ADST_REAL64:  GetSymbolType = "REAL64"
        Case ADST_REAL80:  GetSymbolType = "REAL80"
        Case ADST_BIGTYPE: GetSymbolType = "BIGTYPE"
        Case ADST_VOID:    GetSymbolType = "VOID"
    End Select
End Function

PLC program

PROGRAM MAIN
VAR
    REAL32_1 AT %MB0 : REAL;  (* 1 *)
    REAL32_2 AT %MB4 : REAL;  (* 2 *)
    REAL32_3 AT %MB8 : REAL;  (* 3 *)
    REAL32_4 AT %MB12: REAL;  (* 4 *)
    REAL32_5 AT %MB16: REAL;  (* 5 *)

    REAL64_1 AT %MB20 : LREAL;  (* 6 *)
    REAL64_2 AT %MB28 : LREAL;  (* 7 *)
    REAL64_3 AT %MB36 : LREAL;  (* 8 *)
    REAL64_4 AT %MB44 : LREAL;  (* 9 *)
    REAL64_5 AT %MB52 : LREAL;  (* 10 *)

    INT32_1 AT %MB60 : DINT;  (* 11 *)
    INT32_2 AT %MB64 : DINT;  (* 12 *)
    INT32_3 AT %MB68 : DINT;  (* 13 *)
    INT32_4 AT %MB72 : DINT;  (* 14 *)
    INT32_5 AT %MB76 : DINT;  (* 15 *)

    UINT32_1 AT %MB80 : UDINT;  (* 16 *)
    UINT32_2 AT %MB84 : UDINT;  (* 17 *)
    UINT32_3 AT %MB88 : UDINT;  (* 18 *)
    UINT32_4 AT %MB92 : UDINT;  (* 19 *)
    UINT32_5 AT %MB96 : UDINT;  (* 20 *)

    INT16_1 AT %MB100 : INT;  (* 21 *)
    INT16_2 AT %MB102 : INT;  (* 22 *)
    INT16_3 AT %MB104 : INT;  (* 23 *)
    INT16_4 AT %MB106 : INT;  (* 24 *)
    INT16_5 AT %MB108 : INT;  (* 25 *)

    UINT16_1 AT %MB110 : UINT;  (* 26 *)
    UINT16_2 AT %MB112 : UINT;  (* 27 *)
    UINT16_3 AT %MB114 : UINT;  (* 28 *)
    UINT16_4 AT %MB116 : UINT;  (* 29 *)
    UINT16_5 AT %MB118 : UINT;  (* 30 *)

    INT8_1 AT %MB120 : SINT;  (* 31 *)
    INT8_2 AT %MB121 : SINT;  (* 32 *)
    INT8_3 AT %MB122 : SINT;  (* 33 *)
    INT8_4 AT %MB123 : SINT;  (* 34 *)
    INT8_5 AT %MB124 : SINT;  (* 35 *)

    UINT8_1 AT %MB125 : USINT;  (* 36 *)
    UINT8_2 AT %MB126 : USINT;  (* 37 *)
    UINT8_3 AT %MB128 : USINT;  (* 38 *)
    UINT8_4 AT %MB129 : USINT;  (* 39 *)
    UINT8_5 AT %MB130 : USINT;  (* 40 *)

    BOOL_1 AT %MX131.0 : BOOL;  (* 41 *)
    BOOL_2 AT %MX131.1 : BOOL;  (* 42 *)
    BOOL_3 AT %MX131.2 : BOOL;  (* 43 *)
    BOOL_4 AT %MX131.3 : BOOL;  (* 44 *)
    BOOL_5 AT %MX131.4 : BOOL;  (* 45 *)

    ARRAY_1 : ARRAY[1 .. 10] OF SINT; (* 46 *)
    ARRAY_2 : ARRAY[1 .. 10] OF INT;  (* 47 *)
    ARRAY_3 : ARRAY[1 .. 10] OF DINT; (* 48 *)
    ARRAY_4 : ARRAY[1 .. 10] OF LREAL;(* 49 *)
    ARRAY_5 : ARRAY[1 .. 10] OF BOOL; (* 50 *)
END_VAR

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample04.exe


AdsOcx Visual Basic Example05: Detect/alter status of the router and the PLC

TwinCAT ADS-OCX: Examples

Detect/alter status of the router and the PLC

Task

ADS-OCX provides methods for trapping status changes in the TwinCAT Router and the ADS devices. The events AdsRouterRemove(), AdsRouterShutdown(), AdsRouterStart(), AdsServerStateChanged() and AdsServerSymChanged() are available for this.

Description

If the AdsRouterShutdown() event is called when the TwinCAT Router is stopped, the affected program can react appropriately. When the TwinCAT Router is started, the AdsRouterStart() event is called, in which, for example, the AdsReadVarConnectEx() method can be used to re-establish the connections to the ADS variables. The AdsRouterRemove() event is called if the TwinCAT Router is completely removed from the operating system in the Windows NT/2000 Control Panel.
State changes in ADS devices can be trapped, as well as status changes in the TwinCAT Router. This is particularly significant to the PLC. The AdsServerStateChanged() event can be used to establish whether the PLC has been started or stopped. Changes of the symbol table are reported by the AdsServerSymChanged() event. This happens, for instance, when the PLC program is recompiled and then transferred to the PLC.

Just as the status of an ADS device can be interrogated, it can also be altered. The AdsSyncWriteControlReq() method makes this possible. The PLC can adopt the ADS states STOP and RUN. Using the check button in the example program below, the user can switch between these two states.

Each change of status in the TwinCAT Router results in an appropriate entry in the listbox.

Warning:If a change in the symbol table is detected, it can be that a variable that was addressed by AdsReadVarConnectEx() has been deleted or renamed. When the AdsServerSymChanged() event occurs, all connects and handles should be deleted and then recreated.

Visual Basic 6 program

Option Explicit

'--- Is called at the start ---
Private Sub Form_Load()
    Call lstEvent.Clear
    AdsOcx1.EnableErrorHandling = True
End Sub

'--- Is called when the status of the ADS device changes ---
Private Sub AdsOcx1_AdsServerStateChanged(ByVal nAdsState As ADSOCXLib.ADSSTATE, ByVal nDeviceState As Long)
    Select Case nAdsState
        Case ADSSTATE_INVALID:      lstEvent.AddItem ("PLC invalid")
        Case ADSSTATE_IDLE:         lstEvent.AddItem ("PLC idle")
        Case ADSSTATE_RESET:        lstEvent.AddItem ("PLC reset")
        Case ADSSTATE_INIT:         lstEvent.AddItem ("PLC init")
        Case ADSSTATE_START:        lstEvent.AddItem ("PLC start")
        Case ADSSTATE_RUN:          lstEvent.AddItem ("PLC run")
                                    chkRunStop.Value = 1
        Case ADSSTATE_STOP:         lstEvent.AddItem ("PLC stop")
                                    chkRunStop.Value = 0
        Case ADSSTATE_SAVECFG:      lstEvent.AddItem ("PLC savecfg")
        Case ADSSTATE_LOADCFG:      lstEvent.AddItem ("PLC loadcfg")
        Case ADSSTATE_POWERFAILURE: lstEvent.AddItem ("PLC powerfailure")
        Case ADSSTATE_POWERGOOD:    lstEvent.AddItem ("PLC powergood")
        Case ADSSTATE_ERROR:        lstEvent.AddItem ("PLC error")
    End Select
End Sub

'--- Is called when the symbol table changes ---
Private Sub AdsOcx1_AdsServerSymChanged()
    lstEvent.AddItem ("PLC symbol changed")
End Sub

'--- Is called when the TwinCAT Router is removed ---
Private Sub AdsOcx1_AdsRouterRemove()
    lstEvent.AddItem ("TwinCAT-Router remove")
End Sub

'--- Is called when the TwinCAT Router is stopped ---
Private Sub AdsOcx1_AdsRouterShutdown()
    lstEvent.AddItem ("TwinCAT-Router shutdown")
End Sub

'--- Is called when the TwinCAT Router is started ---
Private Sub AdsOcx1_AdsRouterStart()
    lstEvent.AddItem ("TwinCAT-Router start")
End Sub

'--- Is called by the user ---
Private Sub chkRunStop_Click()
    Dim nState As ADSOCXLib.ADSSTATE
    Dim nRet As Integer
    nState = IIf(chkRunStop.Value = 0, ADSSTATE_STOP, ADSSTATE_RUN)
    Call AdsOcx1.AdsSyncWriteControlReq(nState, 0&, 0&, nRet)
End Sub

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample05.exe


AdsOcx Visual Basic Example06: Send/receive messages via the router

TwinCAT ADS-OCX: Examples

Send/receive messages via the router

Task

ADS devices can send messages to other ADS devices via the TwinCAT Router. They can be received and evaluated there. It is equally possible to write messages to the Windows NT/2000 Event Logger.
The following Visual Basic program receives messages from the PLC and displays them on the screen. It is also possible for messages to be written to the Windows NT/2000 Event Logger from the Visual Basic program.

Description

In order to be able to receive messages, a filter must first be defined using the AdsEnableLogNotification() method. Here the range of port numbers from which ADS device messages are to be received is given. A second parameter additionally states the type of message (error, information or warning). More than one message type can be combined using the OR operator.
Whenever a message is issued by an ADS device and the filter conditions are satisfied, the AdsLogNotification() event is called. The source, type, time and the message itself can be determined from the parameters.
If a message is to be sent with the aid of ADS-OCX, the AdsLogFmtString() method is used. The first parameter contains the message type (error, information or warning). The message text can contain up to four placeholders for numerical values. This allows, for instance, values that only become known at run-time to be transmitted. All messages that are sent with ADS-OCX are automatically written in the Windows NT/2000 Event Logger.

The Windows NT/2000 Event Viewer can be called from the Visual Basic program. This allows all the messages that are in the Event Logger to be examined. The Event Logger and the Event Viewer are standard elements in the Windows NT/2000 suites. You will find more details in the Windows NT/2000 documentation.

The PLC program sends out information cyclically every 5 seconds. This information is also written into the Windows NT/2000 Event Logger. The ADSLOGDINT() PLC function is described in the PLC documentation.

Note:All messages that are sent via the TwinCAT Router can be observed online in the System Manager. This requires logger output to be activated in the View menu.

Windows NT/2000 contains API functions with which the messages that have been saved in the Event Logger can be read again. Unfortunately, Visual Basic does not (yet) have any components with which the messages saved in the Event Logger can be accessed. On pp. 56 ff of issue 6/98 of the basicopro magazine, published by Steingräberverlag (http://www.basicpro.de), there is an article that illustrates the utilisation of the API functions under Visual Basic.

Application

This technique has been found very useful in the development and debugging of applications. A PLC program, for example, or a Visual Basic program can indicate certain internal program states (in the System Manager) and save them (in the Windows NT/2000 Event Logger). It is not necessary to install the development environment (on, for instance, a machine computer) in order to trace the program in this way.

Make sure that not too many messages are transmitted in a short time, as this can cause the performance of the whole system to deteriorate.

Tip:If you want to keep a log of messages in your program (e.g. malfunctions in a machine) you should make use of the TwinCAT Event Logger. This is significantly more powerful than the Windows NT/2000 Event Logger, and is adapted to the requirements of automation technology.

Visual Basic 6 program

Option Explicit

'--- Is called at the start of the program ---
Private Sub Form_Load()
    cboMessageType.ListIndex = 0
    AdsOcx1.EnableErrorHandling = True
    '--- Trap messages ---
    Call AdsOcx1.AdsEnableLogNotification(1, 65535, ADSLOG_MSGTYPE_HINT Or ADSLOG_MSGTYPE_ERROR Or ADSLOG_MSGTYPE_WARN)
End Sub

'--- Is called by ADS-OCX when a message arrives ---
Private Sub AdsOcx1_AdsLogNotification(ByVal dateTime As Date, ByVal nMs As Long, _
                                       ByVal dwMsgCtrl As Long, ByVal nServerPort As Long, _
                                       ByVal szDeviceName As String, ByVal szLogMsg As String)
    '--- Display message ---
    lstMessages.AddItem Format(DateValue(dateTime), "!@@@@@@@@@@") & _
                        Format(TimeValue(dateTime), "!@@@@@@@@@@@@@@@") & _
                        Format(nMs, "000   ") & _
                        Format(szDeviceName, "!@@@@@@@@@@") & _
                        Format(nServerPort, "00000     ") & _
                        szLogMsg
End Sub

'--- Submit message ---
Private Sub cmdSend_Click()
    Dim Para1 As Long
    Dim Para2 As Double
    Dim Para3 As Integer
    Dim Para4 As Integer
    '--- Set parameters ---
    Para1 = CLng(txt1Para.Text)
    Para2 = CDbl(txt2Para.Text)
    Para3 = CInt(txt3Para.Text)
    Para4 = CInt(txt4Para.Text)
    '--- Submit message ---
    Call AdsOcx1.AdsLogFmtString(cboMessageType.ItemData(cboMessageType.ListIndex), _
                                 txtMessage.Text, Para1, Para2, Para3, Para4)
End Sub

'--- Display Windows NT/2000 Event Viewer ---
Private Sub cmdEventViewer_Click()
    Call Shell("eventvwr.exe", vbNormalFocus)
End Sub

'--- Delete list ---
Private Sub cmdClearList_Click()
    Call lstMessages.Clear
End Sub

PLC program

PROGRAM MAIN
VAR
    PLCVarInteger AT %MW0  : INT;
    TP_1  : TP;
    TOGGEL  : BOOL;
    AdsLogResult  : DINT;
END_VAR

TOGGEL := NOT TOGGEL;
TP_1( IN := TOGGEL, PT := t#5s);
IF (TP_1.Q = 0) THEN
    IF (TOGGEL = 0) THEN
        PLCVarInteger := PLCVarInteger + 1;
        AdsLogResult := ADSLOGDINT(ADSLOG_MSGTYPE_HINT OR ADSLOG_MSGTYPE_LOG , 'Message from the PLC: %d', PLCVarInteger);
    END_IF
END_IF

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample06.exe


AdsOcx Visual Basic Example07: Delete a handle of a PLC variable

TwinCAT TcAdsDLL: Examples

Delete a handle of a PLC variable

This Sample shows how to delete a handle of a PLC variable:

Visual Basic 6 program

Dim handle As Long

'--- Is called at the start ---
Private Sub Form_Load()
    txtHandle.Text = handle
End Sub

' --- Is called when "Get Handle" is pressed ---
Private Sub btnGetHandle_Click()
    Call AdsOcx1.AdsCreateVarHandle("MAIN.PLCVar", handle)
    txtHandle.Text = handle
End Sub

' --- Is called when "Release Handle" is pressed ---
Private Sub btnReleaseHandle_Click()
    Call AdsOcx1.AdsDeleteVarHandle(handle)
    handle = 0
    txtHandle.Text = handle
End Sub

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample07.exe


AdsOcx Visual Basic Example09: Event-driven reading (with conversion to another type)

TwinCAT ADS-OCX: Examples

Event-driven reading (with converting notification data)

From TwinCAT 2.8 Build > 743 and above

Task

Four variables of different types are located in the PLC. The variables are to be read in the most effective manner, and the values displayed on a form in Visual Basic. A checkbox can be used to switch between two different connection modes (ADSTRANS_SERVERCYCLE or ADSTRANS_SERVERONCHA). Two buttons can be used to establish or break the connection to the PLC variables.

The PLC variables are structured data types. The PLC sends these as a data block, for instance, to the AdsOcx client. AdsOcx however can only pass variables of a specific data type, including the variant type, as parameters to the VB event routine. The user can make use of the AdsReadVarConvertConnect method to specify the type of the variant variable in the VB event routine in advance. The event data is then copied into the variant variable by AdsOcx, and so passed to the VB event routine. A variant array can also represent a complex structure in the PLC. How much data can be copied into the individual variant elements depends on the type of the individual elements. A few exceptions need to be considered, such as occur with strings (the string length must be appropriately set beforehand) and boolean variables (where a 2-byte VB boolean is formed from 1 byte of data).

The following PLC variables are to be displayed on the form:

  • The value of an enumeration type (enum) is to be read into a long variable and displayed in the label.
  • The value of a structured data type (a structure containing four booleans) is to be read into a long variable and displayed in a checkbox. The checkbox is to be selected if one of the boolean variables in the PLC is TRUE.
  • The value of a string array is to be displayed in a listbox.
  • The value of a structured data type is to be read into a variant array and displayed in a further listbox.

The PLC application

VAR_GLOBAL
    eColors 		: E_Colors := cWhite;
    st4Switches 	: ST_4Switches;
    arr3Strings 	: ARRAY[1..3] OF STRING :=1('First'), 1('Second'),1('Third');
    stBigStruct 	: ST_BigStruct;
END_VAR

Online display of the PLC data:

The definition of the enumeration type:

TYPE E_Colors :
(
    cUnknown,
    cWhite := 1,
    cBlue := 2,
    cRed := 3,
    cBlack
);
END_TYPE

The definition of the structure with four boolean variables:

TYPE ST_4Switches :
STRUCT
    bLevel1 : BOOL;
    bLevel2 : BOOL;
    bLevel3 : BOOL;
    bLevel4 : BOOL;
END_STRUCT
END_TYPE

The definition of the structured data type:

TYPE ST_BigStruct :
STRUCT
    single : REAL;
    long : DINT;
    boolean : BOOL;
    stSub1 : ST_Sub1;
    counter : DINT;
    datetime : DT := DT#2003-01-20-15:12:44;
END_STRUCT
END_TYPE

This, in turn, has two substructures:

TYPE ST_Sub1 :
STRUCT
    bFirst : BOOL;
    bSecond : BOOL;
    bThird : BOOL;
    stSub2 : ST_Sub2;
END_STRUCT
END_TYPE
TYPE ST_Sub2 :
STRUCT
    integer : INT;
    double : LREAL;
    string20 : STRING(20) := 'Unknown';
END_STRUCT
END_TYPE

Visual Basic 6 program

Option Explicit
Dim adsErr As Long
Dim hConnect_EnumVar As Long
Dim hConnect_4Switches As Long
Dim hConnect_StringArray As Long
Dim hConnect_BigStruct As Long

The connection to the first PLC runtime system is established as the form is loaded:

Private Sub Form_Load()
    AdsOcx1.AdsAmsServerNetId = AdsOcx1.AdsAmsClientNetId
    AdsOcx1.AdsAmsServerPort = 801
    AdsOcx1.EnableErrorHandling = True
End Sub

A mouse click on the AdsReadVarConvertConnect button establishes a connection to the PLC variables. When successful, the AdsReadVarConvertConnect method returns a handle. The connection can only be identified, and later removed, by reference to this handle.

1. The enumeration type in the PLC only occupies two bytes of memory. These two bytes are read into a long variable (four bytes) and are returned in the event function. Using the VB integer data type would be just as effective.

2. The 4 boolean values in the structure variable occupy 4 individual bytes of PLC memory in the PLC. These are read into a long variable, and returned as a long variable in the event function.

3. The strings in the array occupy a total of 243 bytes of memory in the PLC (defined string length + 1 byte for the null termination)*3. The length of the individual VB strings must correspond to the length of the PLC strings in order to be able to separate the individual strings. If the string has zero length, no event data is copied into a string variable.

4. The structure variable can be read into a one-dimensional variant array. The individual array elements can be of different types. Before establishing the connection, however, the individual array elements must be initialised with the appropriate type.

Private Sub cmdConnect_Click()
    Dim adsTransMode As ADSOCXTRANSMODE
    adsTransMode = IIf(chkTransMode.Value = vbChecked, ADSTRANS_SERVERCYCLE, ADSTRANS_SERVERONCHA)

    'Connects to enum var
    Dim convertedEnumVar As Long
    adsErr = AdsOcx1.AdsReadVarConvertConnect(".eColors", adsTransMode, 300, hConnect_EnumVar, convertedEnumVar, lblEnum)

    'Connects to struct with 4 boolean variables
    Dim converted4Switches As Long
    adsErr = AdsOcx1.AdsReadVarConvertConnect(".st4Switches", adsTransMode, 300, hConnect_4Switches, converted4Switches, chk4Switches)

    'Connects to array of strings
    Dim convertedStringArray(1 To 3) As String
    Dim i As Integer
    For i = LBound(convertedStringArray) To UBound(convertedStringArray)
        convertedStringArray(i) = String(81, "#")
    Next i
    adsErr = AdsOcx1.AdsReadVarConvertConnect(".arr3Strings", adsTransMode, 300, hConnect_StringArray, convertedStringArray, lstStringArray)

    'Connects to struct variable
    Dim convertedBigStruct(1 To 11) As Variant
    convertedBigStruct(1) = CSng(0) 		'stBigStruct.single
    convertedBigStruct(2) = CLng(0)		'stBigStruct.long
    convertedBigStruct(3) = CBool(False)		'stBigStruct.boolean
    convertedBigStruct(4) = CBool(False)		'stBigStruct.stSub1.bFirst
    convertedBigStruct(5) = CBool(False)		'stBigStruct.stSub1.bSecond
    convertedBigStruct(6) = CBool(False)		'stBigStruct.stSub1.bThird
    convertedBigStruct(7) = CInt(0)		'stBigStruct.stSub1.stSub2.integer
    convertedBigStruct(8) = CDbl(0)		'stBigStruct.stSub1.stSub2.double
    convertedBigStruct(9) = CStr(String(21, "*"))	'stBigStruct.stSub1.stSub2.string20
    convertedBigStruct(10) = CLng(0)		'stBigStruct.counter
    convertedBigStruct(11) = CDate(0)		'stBigStruct.datetime
    adsErr = AdsOcx1.AdsReadVarConvertConnect(".stBigStruct", adsTransMode, 300, hConnect_BigStruct, convertedBigStruct, lstBigStruct)

    cmdConnect.Enabled = False
    cmdDisconnect.Enabled = True
End Sub

A mouse click on the AdsDisconnectEx button will break the connections to the PLC variables:

Private Sub cmdDisconnect_Click()
    adsErr = AdsOcx1.AdsDisconnectEx(hConnect_EnumVar)
    adsErr = AdsOcx1.AdsDisconnectEx(hConnect_4Switches)
    adsErr = AdsOcx1.AdsDisconnectEx(hConnect_StringArray)
    adsErr = AdsOcx1.AdsDisconnectEx(hConnect_BigStruct)

    cmdConnect.Enabled = True
    cmdDisconnect.Enabled = False
End Sub

The AdsReadConvertConnectUpdate event routine. This event routine is called cyclically (if ADSTRANS_SERVERCYCLE is selected) or is only called when the value of the PLC variable has changed (if ADSTRANS_SERVERONCHA is selected). The hUser parameter can be used to be able to assign the event data to the appropriate control (label, checkbox, listbox).

Private Sub AdsOcx1_AdsReadConvertConnectUpdate(ByVal dateTime As Date, ByVal nMs As Long, ByVal hConnect As Long, data As Variant, Optional hUser As Variant)
    Dim i As Integer
    If TypeOf hUser Is CheckBox Then

        chk4Switches.Value = IIf(data = 0, vbUnchecked, vbChecked)
        chk4Switches.Caption = IIf(data = 0, "ALL disbled", "At least ONE enabled")

    ElseIf TypeOf hUser Is ListBox Then

        If hUser Is lstStringArray Then
            Call lstStringArray.Clear
            For i = LBound(data) To UBound(data)
                Call lstStringArray.AddItem("Type: " & TypeName(data(i)) & " Value: " & data(i))
            Next i
        ElseIf hUser Is lstBigStruct Then
            Call lstBigStruct.Clear
            For i = LBound(data) To UBound(data)
                Call lstBigStruct.AddItem("Type: " & TypeName(data(i)) & " Value: " & data(i))
            Next i
        End If

    Else 'lblEnum
        Dim objLabel As Label
        Set objLabel = hUser
        objLabel.Caption = "eColors: " & data
    End If
End Sub

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-OCX Sample09.exe


TcAdsDll Visual Basic Example01: Reading and writing of variables with Visual Basic

TwinCAT TcADSDLL: Examples

Reading and writing of variables with Visual Basic

This sample program demonstrates how the TcAdsDll COM interfaces can be addressed by Visual Basic for reading variables from the PLC or writing to the PLC.

In the load event function of Visual Basic program the TcClient object is created via CreateObject. The Connect method is used for establishing a connection with the PLC. "0.0.0.0.0.0" is transferred as NetID, thereby establishing a connection with the local computer.

The variables are read from the PLC by pressing the Read button. To this end the Read method of the TcAdsSync object is called. A byte array with the length of the variables to be read is transferred to this method. This array then has to be copied into the corresponding VB data type. If the data layout is identical, the RtlMoveMemory API routine can be used for this purpose. During writing into the PLC, the byte array is first filled with the required data, after which the Write method is called.

Visual Basic 6 program

Option Explicit
Dim WithEvents ads As TcAdsDll.TcAdsSync

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(lpDest As Any, lpSrc As Any, ByVal Length As Long)

Private Sub Form_Load()
  On Error GoTo errFunc
  Dim client As TcAdsDll.TcClient
  Dim netId As AmsNetId

  Set client = CreateObject("TcAdsDll.TcClient")
  netId.b(0) = 0
  netId.b(1) = 0
  netId.b(2) = 0
  netId.b(3) = 0
  netId.b(4) = 0
  netId.b(5) = 0
  Set ads = client.Connect(netId, 801)

  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Sub cmdRead_Click()
  On Error Resume Next
  TextBoolean.Text = ReadBoolean(&H4020&, 0)
  TextInteger.Text = ReadInteger(&H4020&, 2)
  TextLong.Text = ReadLong(&H4020&, 4)
  TextSingle.Text = ReadSingle(&H4020&, 8)
  TextDouble.Text = ReadDouble(&H4020&, 12)
End Sub

Private Sub cmdWrite_Click()
On Error Resume Next
  Call WriteBoolean(&H4020&, 0, TextBoolean.Text)
  Call WriteInteger(&H4020&, 2, TextInteger.Text)
  Call WriteLong(&H4020&, 4, TextLong.Text)
  Call WriteSingle(&H4020&, 8, TextSingle.Text)
  Call WriteDouble(&H4020&, 12, TextDouble.Text)
End Sub

Private Sub WriteBoolean(indexGroup As Long, indexOffset As Long, val As Boolean)
  On Error GoTo errFunc
  Dim arrRead(0 To 0) As Byte
  Dim cbLen As Long

  cbLen = 1
  If (val = True) Then
    arrRead(0) = 1
  Else
    arrRead(0) = 0
  End If

  Call ads.Write(indexGroup, indexOffset, cbLen, arrRead(0))

  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Sub WriteInteger(indexGroup As Long, indexOffset As Long, val As Integer)
  On Error GoTo errFunc
  Dim arrRead(0 To 1) As Byte
  Dim cbLen As Long

  cbLen = 2
  Call CopyMemory(arrRead(0), val, cbLen)
  Call ads.Write(indexGroup, indexOffset, cbLen, arrRead(0))

  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Sub WriteLong(indexGroup As Long, indexOffset As Long, val As Long)
  On Error GoTo errFunc
  Dim arrRead(0 To 3) As Byte
  Dim cbLen As Long

  cbLen = 4
  Call CopyMemory(arrRead(0), val, cbLen)
  Call ads.Write(indexGroup, indexOffset, cbLen, arrRead(0))

  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Sub WriteSingle(indexGroup As Long, indexOffset As Long, val As Single)
  On Error GoTo errFunc
  Dim arrRead(0 To 3) As Byte
  Dim cbLen As Long

  cbLen = 4
  Call CopyMemory(arrRead(0), val, cbLen)
  Call ads.Write(indexGroup, indexOffset, cbLen, arrRead(0))

  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Sub WriteDouble(indexGroup As Long, indexOffset As Long, val As Double)
  On Error GoTo errFunc
  Dim arrRead(0 To 7) As Byte
  Dim cbLen As Long

  cbLen = 8
  Call CopyMemory(arrRead(0), val, cbLen)
  Call ads.Write(indexGroup, indexOffset, cbLen, arrRead(0))

  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Function ReadBoolean(indexGroup As Long, indexOffset As Long) As Boolean
  On Error GoTo errFunc
  Dim arrRead(0 To 0) As Byte
  Dim cbRead As Long
  Dim cbLen As Long
  Dim val As Boolean

  cbLen = 1
  Call ads.Read(indexGroup, indexOffset, cbLen, cbRead, arrRead(0))

  If (arrRead(0) = 0) Then
    val = False
  Else
    val = True
  End If
  ReadBoolean = val

  Exit Function
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Function

Private Function ReadInteger(indexGroup As Long, indexOffset As Long) As Integer
  On Error GoTo errFunc
  Dim arrRead(0 To 1) As Byte
  Dim cbRead As Long
  Dim cbLen As Long
  Dim val As Integer

  cbLen = 2
  Call ads.Read(indexGroup, indexOffset, cbLen, cbRead, arrRead(0))
  Call CopyMemory(val, arrRead(0), cbLen)
  ReadInteger = val

  Exit Function
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Function

Private Function ReadLong(indexGroup As Long, indexOffset As Long) As Long
On Error GoTo errFunc
  Dim arrRead(0 To 3) As Byte
  Dim cbRead As Long
  Dim cbLen As Long
  Dim val As Long

  cbLen = 4
  Call ads.Read(indexGroup, indexOffset, cbLen, cbRead, arrRead(0))
  Call CopyMemory(val, arrRead(0), cbLen)
  ReadLong = val

  Exit Function
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Function

Private Function ReadSingle(indexGroup As Long, indexOffset As Long) As Single
On Error GoTo errFunc
  Dim arrRead(0 To 3) As Byte
  Dim cbRead As Long
  Dim cbLen As Long
  Dim val As Single

  cbLen = 4
  Call ads.Read(indexGroup, indexOffset, cbLen, cbRead, arrRead(0))
  Call CopyMemory(val, arrRead(0), cbLen)
  ReadSingle = val

  Exit Function
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Function

Private Function ReadDouble(indexGroup As Long, indexOffset As Long) As Double
  On Error GoTo errFunc
  Dim arrRead(0 To 7) As Byte
  Dim cbRead As Long
  Dim cbLen As Long
  Dim val As Double

  cbLen = 8
  Call ads.Read(indexGroup, indexOffset, cbLen, cbRead, arrRead(0))
  Call CopyMemory(val, arrRead(0), cbLen)
  ReadDouble = val

  Exit Function
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Function

PLC program

PROGRAM MAIN
VAR
  PLCBoolVar AT %MX0.0 : BOOL := TRUE;
  PLCIntVar AT %MB2 : INT := 75;
  PLCDIntVar AT %MB4 : DINT := 43234532;
  PLCRealVar AT %MB8 : REAL := 3.1415;
  PLCLRealVar AT %MB12 : LREAL := 3.141592654;
END_VAR

;

Language / IDE | Unpack the sample program
Visual Basic 6 | TcAdsDll_ComSample01.exe


TcAdsDll Visual Basic Example02: Event-driven reading with Visual Basic

TwinCAT TcAdsDLL: Examples

Event-driven reading with Visual Basic

In the load event of the form, a connection to the PLC variables is established via the AddDeviceNotification method. If a PLC variable changes, the DeviceNotification event of the TcAdsSync object is called. The connections are cancelled via the DelDeviceNotification method in the unload event or by pressing the Stop button.

Visual Basic 6 program

Option Explicit
Dim WithEvents ads As TcAdsDll.TcAdsSync
Attribute ads.VB_VarHelpID = -1
Dim hValInteger As Long
Dim hValDouble As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
 (lpDest As Any, lpSrc As Any, ByVal Length As Long)

Private Sub Form_Load()
On Error GoTo errFunc
  Dim client As TcAdsDll.TcClient
  Dim netId As AmsNetId

  Set client = CreateObject("TcAdsDll.TcClient")
  netId.b(0) = 0
  netId.b(1) = 0
  netId.b(2) = 0
  netId.b(3) = 0
  netId.b(4) = 0
  netId.b(5) = 0
  Set ads = client.Connect(netId, 801)

  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Sub cmdStart_Click()
  Dim delay As Long
  Dim cycle As Long

  delay = 0
  cycle = 1000000 '100ms

  If hValInteger = 0 Then
    hValInteger = ads.AddDeviceNotification(&H4020&, 2, 2, ADSTRANS_SERVERONCHA, delay, cycle)
  End If
  If hValDouble = 0 Then
    hValDouble = ads.AddDeviceNotification(&H4020&, 12, 8, ADSTRANS_SERVERONCHA, delay, cycle)
  End If
  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

Private Sub cmdStop_Click()
  Call DeleteNotifications
End Sub

Private Sub Form_Unload(Cancel As Integer)
  Call DeleteNotifications
End Sub

Private Sub ads_DeviceNotification(pTime As TcAdsDll.TimeStamp, ByVal hNotification As Long, ByVal cbLen As Long, pData As Byte)
  Dim integerVal As Integer
  Dim doubleVal As Double

  If (hNotification = hValInteger And cbLen = 2) Then
    Call CopyMemory(integerVal, pData, cbLen)
    TextInteger.Text = integerVal
  ElseIf (hNotification = hValDouble And cbLen = 8) Then
    Call CopyMemory(doubleVal, pData, cbLen)
    TextDouble.Text = doubleVal
  End If
End Sub

Private Sub DeleteNotifications()
On Error GoTo errFunc
  If hValInteger <> 0 Then
    Call ads.DelDeviceNotification(hValInteger)
    hValInteger = 0
  End If
  If hValDouble <> 0 Then
    Call ads.DelDeviceNotification(hValDouble)
    hValDouble = 0
  End If
  Exit Sub
  errFunc:
    MsgBox "Error: (0x" & Format(Hex(Err.Number), "00000000") & "), " & Err.Description
End Sub

PLC program

PROGRAM MAIN
VAR
  PLCBoolVar AT %MX0.0 : BOOL := TRUE;
  PLCIntVar AT %MB2 : INT := 75;
  PLCDIntVar AT %MB4 : DINT := 43234532;
  PLCRealVar AT %MB8 : REAL := 3.1415;
  PLCLRealVar AT %MB12 : LREAL := 3.141592654;
END_VAR

;

Language / IDE | Unpack the sample program
Visual Basic 6 | TcAdsDll_ComSample02.exe


TwinCAT.Ads in Visual Studio .NET (VB)

TwinCAT ADS .NET component

Integration in Microsoft Visual Studio .NET (VB)

Create new project

The TwinCAT.Ads component can only be integrated in an existing project. Start Visual Studio and select the following command from the menu: File->New->Project...

In the New Project dialog please select as project type: Visual Basic->Windows Forms Application.

Adding a reference

To select the class library TwinCAT.Ads select the command Add Reference... from the Project menu. The Add Reference dialog opens.
Alternatively you double-click on My Project (1) to open the Project Properties window and click the Add... button (3) under Reference (2).

In this dialog you should select the Browse tab and navigate to file TwinCAT.Ads.dll. Confirm the selected dll with OK. By default the TwinCAT.Ads component is located in the ..TwinCAT\Ads Api\.NET\ folder.

Add namespace

All class library types belong to the TwinCAT.Ads namespace. Therefore the following lines should be added at the beginning of the source text:

Imports TwinCAT.Ads
Imports System.IO

This enables access to the data types defined in TwinCAT.Ads without having to specify the namespace repeatedly. The TcAdsClient class is the core of the TwinCAT.Ads class library and enables communication with an Ads device. The first step is to create an instance of the TcAdsClient class. Then make use of the Connect method to establish a connection with the ADS device.

Establishing a connection to an Ads device:

Private tcAds As TcAdsClient
...

tcAds = New TcAdsClient
tcAds.Connect("1.2.3.4.1.1", 801)

...

The data are transmitted with the aid of the AdsStream class, which is derived from System.IO.MemoryStream. The class System.IO.BinaryWriter can be used to write data to the stream, the class System.IO.BinaryReader can be used to read data from the stream.

Reading a PLC variable:

' creates a stream with a length of 4 byte
Dim ds = New AdsStream(4)
Dim br = New BinaryReader(ds)

' reads a DINT from PLC
tcAds.Read(&H4020, 0, ds)
ds.Position = 0
TextBox1.Text = br.ReadInt32().ToString()

Writing a PLC variable:

' creates a stream with a length of 4 byte
Dim ds = New AdsStream(4)
Dim bw = New BinaryWriter(ds)

ds.Position = 0
bw.Write(New Integer = 100)

' writes a DINT to PLC
tcAds.Write(&H4020, 0, ds)

Separating a connection to an Ads device:

tcAds.Dispose();

PLC program:

PROGRAM MAIN
VAR
    test AT%MD0 : DINT;
END_VAR
test := test + 1


Visual Basic ADS.NET Sample01

TwinCAT Sample - VB - ADS .NET

Accessing an array in the PLC

Task

The PLC contains an array that is to be read by the .Net application using a read command.

Description

The PLC contains an array of 100 elements of type integer (2 bytes). The array in the PLC is to be filled with the values from 3500 to 3599.

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.IO

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

...

#End Region

    Dim hVar As Integer
    Dim tcClient As TcAdsClient

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        tcClient = New TcAdsClient()
        tcClient.Connect(801)

        Try
            hVar = tcClient.CreateVariableHandle("MAIN.PLCVar")
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try

    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        'Release resources
        Try
            tcClient.DeleteVariableHandle(hVar)
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
        tcClient.Dispose()
    End Sub

    Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click
        Try
            Dim dataStream As New AdsStream(100 * 2)
            Dim binRead As New BinaryReader(dataStream)

            'Read complete array
            tcClient.Read(hVar, dataStream)

            lbArray.Items.Clear()
            dataStream.Position = 0
            Dim i As Integer
            For i = 0 To 99
                lbArray.Items.Add(binRead.ReadInt16().ToString())
            Next
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

End Class

PLC program

PROGRAM MAIN
VAR
    PLCVar : ARRAY [0..99] OF INT;
    Index: BYTE;
END_VAR
FOR Index := 0 TO 99 DO
    PLCVar[Index] := 3500 + INDEX;
END_FOR

Language / IDE | Unpack the sample program
Visual Basic (for .NET framework) | Sample01.exe


Visual Basic ADS.NET Sample02

TwinCAT Sample - VB - ADS .NET

Transmitting a structure to the PLC

Task

A structure is to be written into the PLC by the .NET application. The elements in the structure have different data types.

Description

The structure we want to write into the PLC:

TYPE PLCStruct
STRUCT
  intVal   : INT;  (*Offset 0*)
  dintVal  : DINT; (*Offset 2*)
  byteVal  : SINT; (*Offset 6*)
  lrealVal : LREAL;(*Offset 7*)
  realVal  : REAL; (*Offset 15 --> Total size 19 Bytes*)
END_STRUCT
END_TYPE

The AdsStream object is initialized with a size of 19 bytes in the write event method. This is equivalent to the size of the data type PLCStruct in the SPS. A BinaryWriter is used to write the data from the textboxes to the stream. After setting the stream position to 0 the individual fields are written to the stream. Finally the total stream is written into the SPS with the help of TcAdsClient.Write.

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.IO

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

...

#End Region

    Dim hVar As Integer
    Dim tcClient As TcAdsClient

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Create instance of TcAdsClient
        tcClient = New TcAdsClient()

        ' Establish connection to local system port 801
        tcClient.Connect(801)

        Try
            hVar = tcClient.CreateVariableHandle("MAIN.PLCVar")
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        ' Release resources
        Try
            tcClient.DeleteVariableHandle(hVar)
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
        tcClient.Dispose()
    End Sub

    Private Sub btnWrite_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnWrite.Click
        Dim dataStream As New AdsStream(19)
        Dim binWrite As New BinaryWriter(dataStream)

        dataStream.Position = 0

        Try
            binWrite.Write(Short.Parse(tbInt.Text))
            binWrite.Write(Integer.Parse(tbDint.Text))
            binWrite.Write(Byte.Parse(tbByte.Text))
            binWrite.Write(Double.Parse(tbLReal.Text))
            binWrite.Write(Single.Parse(tbReal.Text))

            tcClient.Write(hVar, dataStream)

        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub
End Class

PLC program

TYPE PLCStruct :
STRUCT
    intVal : INT;
    dintVal : DINT;
    sintVal : SINT;
    lrealVal : LREAL;
    realVal : REAL;
END_STRUCT
END_TYPE
PROGRAM MAIN
VAR
    PLCVar:PLCStruct;
END_VAR

Language / IDE | Unpack the sample program
Visual Basic (for .NET framework) | Sample02.exe


Visual Basic ADS.NET Sample03

TwinCAT Sample - VB - ADS .NET

Event driven reading

Task

There are 7 global variables in the PLC. Each of these PLC variables is of a different data type. The values of the variables should be read in the most effective manner, and the value with its timestamp is to be displayed on a form.

Description

In the form's MainForm_Load event, a connection to each of the PLC variables is created with the TcAdsClient.AddDeviceNotification() method. The handle for this connection is stored in a array.
The parameter TransMode specifies the type of data exchange. AdsTransMode.OnChange has been selected here. This means that the value of the PLC variable is only transmitted if its value within the PLC has changed (see the AdsTransMode data type). The parameter cycleTime indicates that the PLC is to check whether the corresponding variable has changed every 100 ms. MaxDelay allows to collect notification for a specified interval. If the maxDelay elapse, all notifications will be send at once.
When the PLC variable changes, the TcAdsClient.AdsNotification() event is called. The parameter e of the event handling method is of the type AdsNotificationEventArgs and contains the time stamp, the handle, the value and the control in which the value is to be displayed. The connections are released again in the MainForm_FormClosing event by means of the TcAdsClient.DeleteDeviceNotification() method. It is essential that you do this, since every connection established by TcAdsClient.AddDeviceNotification() uses resources.
You should also choose appropriate values for the cycle time, since too many write / read operations load the system so heavily that the user interface becomes much slower.

Advice: Don't use time intensive executions in callbacks (OnNotification()).

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.IO

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "
...

#End Region

    ' Handle events (1): without withevents declaration
    Private tcClient As TcAdsClient
    ' Handle events (2):You can also declare tcClient as:
    ' Private WithEvents tcClient As TcAdsClient
    Private hConnect() As Integer
    Private dataStream As AdsStream
    Private binRead As BinaryReader

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim dataStream As New AdsStream(31)
        ' Set encoding to ASCII to be able to read strings
        binRead = New BinaryReader(dataStream, System.Text.Encoding.ASCII)
        ' Create instance of TcAdsClient class
        tcClient = New TcAdsClient()
        ' Establish connection to local port 801
        tcClient.Connect(801)

        ReDim hConnect(7)

        Try
            hConnect(0) = tcClient.AddDeviceNotification("MAIN.boolVal", dataStream, 0, 1, _
                AdsTransMode.OnChange, 100, 0, tbBool)
            hConnect(1) = tcClient.AddDeviceNotification("MAIN.intVal", dataStream, 1, 2, _
                AdsTransMode.OnChange, 100, 0, tbInt)
            hConnect(2) = tcClient.AddDeviceNotification("MAIN.dintVal", dataStream, 3, 4, _
                AdsTransMode.OnChange, 100, 0, tbDint)
            hConnect(3) = tcClient.AddDeviceNotification("MAIN.sintVal", dataStream, 7, 1, _
                AdsTransMode.OnChange, 100, 0, tbSint)
            hConnect(4) = tcClient.AddDeviceNotification("MAIN.lrealVal", dataStream, 8, 8, _
                AdsTransMode.OnChange, 100, 0, tbLreal)
            hConnect(5) = tcClient.AddDeviceNotification("MAIN.realVal", dataStream, 16, 4, _
                AdsTransMode.OnChange, 100, 0, tbReal)
            hConnect(6) = tcClient.AddDeviceNotification("MAIN.stringVal", dataStream, 20, 11, _
                AdsTransMode.OnChange, 100, 0, tbString)

            ' Handle events (1):
            AddHandler tcClient.AdsNotification, AddressOf OnNotification
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        Try
            tcClient.Dispose()
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    ' Handle events (2):
    ' Private Sub OnNotification(ByVal sender As Object, ByVal e As AdsSyncNotificationEventArgs) Handles tcClient.AdsSyncNotification
    ' Handle events (1):
    Private Sub OnNotification(ByVal sender As Object, ByVal e As AdsNotificationEventArgs)
        Dim time As New DateTime()
        time = DateTime.FromFileTime(e.TimeStamp)
        e.DataStream.Position = e.Offset
        Dim strValue As String = ""

        If (e.NotificationHandle = hConnect(0)) Then
            strValue = binRead.ReadBoolean().ToString()
        ElseIf (e.NotificationHandle = hConnect(1)) Then
            strValue = binRead.ReadInt16().ToString()
        ElseIf (e.NotificationHandle = hConnect(2)) Then
            strValue = binRead.ReadInt32().ToString()
        ElseIf (e.NotificationHandle = hConnect(3)) Then
            strValue = binRead.ReadByte().ToString()
        ElseIf (e.NotificationHandle = hConnect(4)) Then
            strValue = binRead.ReadDouble().ToString()
        ElseIf (e.NotificationHandle = hConnect(5)) Then
            strValue = binRead.ReadSingle().ToString()
        ElseIf (e.NotificationHandle = hConnect(6)) Then
            strValue = New String(binRead.ReadChars(11))
        End If

        Dim tb As New TextBox()
        tb = CType(e.UserData, TextBox)
        tb.Text = String.Format("DateTime: {0},{1}ms; {2}", time, time.Millisecond, strValue)
    End Sub
End Class

PLC program

PROGRAM MAIN
VAR
    boolVal : BOOL;
    intVal : INT;
    dintVal : DINT;
    sintVal : SINT;
    lrealVal : LREAL;
    realVal : REAL;
    stringVal : STRING(10);
END_VAR
;

Language / IDE | Unpack the sample program
Visual Basic (for .NET framework) | Sample03.exe


Visual Basic ADS.NET Sample04

TwinCAT Sample - VB - ADS .NET

Reading and writing of string variables

Task

A .Net application should read a string from the PLC and write a string to the PLC.

Description

The PLC contains the string: MAIN.text.

In the MainForm_Load event method a new instance of the class TcAdsClient is created. Then the method TcAdsClient.Connect of the TcAdsClient object is called to establish a connection to the port 801. Finally the method TcAdsClient.CreateVariableHandle is used to fetch the handle of the PLC variable . When the program finishes, the Dispose method of the TcAdsClient object is called. When the user clicks the "Read" button on the form, the string is read by means of the TcAdsClient.Read method and is displayed in the text box. When the user clicks the "Write" button ont the form, the string is wirtten to PLC and is displayed in the text box.

TwinCAT.Ads.NET version < 1.0.0.10:

The length of the AdsStream must be equal to size of the string in the PLC plus 1 (inclusive the terminating zero character). The parameter System.Text.Encoding.ASCII has to be passed to constructor of the BinaryReader class. This is necessary for the BinaryReader to be able to convert the ANSI characters to the char type.

TwinCAT.Ads.NET version >= 1.0.0.10:

The classes AdsBinaryReader and AdsBinaryWriter can be used to read and write strings ( see commented section in sample program ). These classes derive from the BinaryReader/Writer classes. To read a string from the stream one has to call the method AdsBinaryReader.ReadPlcString. To write a string to the stream one has to call the method AdsBinaryWriter.WritePLCString. The length of the AdsStream must be equal to size of the string in the PLC (without terminating zero character). The length is passed to these method and have to be equal to the length of the string in the PLC (without the terminating zero character).

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.IO

Public Class Form1
    Inherits System.Windows.Forms.Form

    Private adsClient As TcAdsClient
    Private varHandle As Integer

#Region " Windows Form Designer generated code "

...

#End Region
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            adsClient = New TcAdsClient
            adsClient.Connect(801)
            varHandle = adsClient.CreateVariableHandle("MAIN.text")
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        adsClient.Dispose()
    End Sub

    Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click
        Dim length As Integer
        Dim text As String
        Dim dataStream As AdsStream
        Dim reader As BinaryReader
        Try
            'length of the stream = length of string in sps + 1
            dataStream = New AdsStream(31)
            reader = New BinaryReader(dataStream, System.Text.Encoding.ASCII)
            length = adsClient.Read(varHandle, dataStream)
            text = New String(reader.ReadChars(length))
            'necessary if you want to compare the string to other strings
            'text = text.Substring(0, text.IndexOf(Chr(0)))
            textBox1.Text = text
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub btnWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWrite.Click
        Dim dataStream As AdsStream
        Dim writer As BinaryWriter
        Dim c As Char
        Try
            'length of the stream = length of string + 1
            dataStream = New AdsStream(textBox1.Text.Length + 1)
            writer = New BinaryWriter(dataStream, System.Text.Encoding.ASCII)
            writer.Write(textBox1.Text.ToCharArray())
            'add terminating zero
            writer.Write(Chr(0))
            adsClient.Write(varHandle, dataStream)
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try

    End Sub

End Class

PLC program

PROGRAM MAIN
VAR
    text : STRING[30] := 'hello';
END_VAR
;

Language / IDE | Unpack the sample program
Visual Basic (for .NET framework) | Sample04.exe TwinCAT.Ads.NET v1.0.0.10 and higher Sample04b.exe


Visual Basic ADS.NET Sample05

TwinCAT Sample - VB - ADS .NET

Reading and writing of DATE/TIME variables

From TwinCAT.Ads.NET version >= 1.0.0.10

Task

A .Net application should read and write a date and a time.

Description

The PLC program contains variables of following type:

  • MAIN.Time1 vom Typ TIME;
  • MAIN.Date1 vom Typ DATE;

In the MainForm_Load event method a new instance of the class TcAdsClient is created. Then the method TcAdsClient.Connect of the TcAdsClient object is called to establish a connection to the port 801. Finally the method TcAdsClient.CreateVariableHandle is used to fetch the handle of the PLC variables . When the program finishes, the MainForm_FormClosing event is called and the Dispose method of the TcAdsClient object is called.

When the user clicks the "Read" button on the form, the variables MAIN.Time1, MAIN.Date1 and so on are read by means of the TcAdsClient.Read method. The class AdsBinaryReader, that derives from BinaryReader, is used to read the time and date from the PLC. The method AdsBinaryReader.ReadPlcTIME reads the time from the AdsStream and converts it to the .NET type: TimeSpan. The method AdsBinaryReader.ReadPlcDATE reads the date from the AdsStream and converts it to the .NET type: DateTime.

When the user clicks the "Write" button ont the form, the variables MAIN.Time1 and MAIN.Date1 are wirtten to the PLC. The class AdsBinaryWriter, that derives from BinaryWriter, is used to write the time and date to the PLC. The method AdsBinaryWriter.WritePlcType writes the .NET types TimeSpan and DateTime in the PLC format for time and date types to the AdsStream.

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.IO

Public Class Form1
    Inherits System.Windows.Forms.Form

    Private adsClient As TcAdsClient
    Private varHandles(1) As Integer

#Region " Windows Form Designer generated code "

...

#End Region
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            adsClient = New TcAdsClient
            adsClient.Connect(801)
            varHandles(0) = adsClient.CreateVariableHandle("MAIN.Time1")
            varHandles(1) = adsClient.CreateVariableHandle("MAIN.Date1")
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        adsClient.Dispose()
    End Sub

    Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click
        Dim length As Integer
        Dim text As String
        Dim dataStream As AdsStream
        Dim reader As AdsBinaryReader
        Try
            dataStream = New AdsStream(30)
            reader = New AdsBinaryReader(dataStream)
            adsClient.Read(varHandles(0), dataStream)
            textBox1.Text = reader.ReadPlcTIME().ToString()

            dataStream.Position = 0
            adsClient.Read(varHandles(1), dataStream)
            textBox2.Text = reader.ReadPlcDATE().ToString()
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub btnWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWrite.Click
        Dim dataStream As AdsStream
        Dim writer As AdsBinaryWriter
        Dim c As Char
        Try
            dataStream = New AdsStream(4)
            writer = New AdsBinaryWriter(dataStream)
            writer.WritePlcType(TimeSpan.Parse(textBox1.Text))
            adsClient.Write(varHandles(0), dataStream)

            dataStream.Position = 0
            writer.WritePlcType(DateTime.Parse(textBox2.Text))
            adsClient.Write(varHandles(1), dataStream)
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub
End Class

PLC program

PROGRAM MAIN
VAR
    Time1 	:TIME := T#521h33m23s231ms;
    Date1 	:DATE := D#1993-06-12;
END_VAR
;

Language / IDE | Unpack the sample program
Visual Basic (for .NET framework) | Sample05.exe


Visual Basic ADS.NET Sample06

TwinCAT Sample - VB - ADS .NET

Read PLC variable declaration

From TwinCAT.Ads.NET version >= 1.0.0.12

Task

All variables that are declared in the PLC should be displayed in a tree view.

Description

In the MainForm_Load method an instance of the TcAdsSymbolInfoLoader is created with a call to TcAdsClient.CreateSymbolInfoLoader. This class is responsible for loading the symbol information from the PLC. By clicking the Load Symbols button the symbols are loaded by means of the TcAdsSymbolInfoLoader.GetFirstSymbol method. This method returns the first loaded symbol as a TcAdsSymbolInfo object. The methods TcAdsSymbolInfo.NextSymbol und TcAdsSymbolInfo.FirstSubSymbol are used to iterate over the symbols and to display the symbols hierarchically in the tree view. If the check box flat is checked, the symbols are displayed in a flat list. In this case the application enumerates over the symbols loaded by the TcAdsSymbolInfoLoader object. This includes the sub symbols.

By selecting a tree view item the edit boxes are filled with the symbol information. Additionally the value of the variable is read with the help of TcAdClient.ReadSymbol and is displayed in the value edit box. To write a value to a variable one has to click the Write Value button, which leads to a call of TcAdsCient.WriteSymbol.

To read the symbol information for a specific variable, one can either click the Read Symbol Info or the Find Symbol button. In the first case the method TcAdsClient.ReadSymbolInfo is called. This leads to an ADS call, to load the information for this symbol from the PLC. In the second case the method TcAdsSymbolInfoLoader.FindSymbol is called, to search for the symbol in the list of the previous loaded symbols. If no symbols have been loaded before, all symbols are loaded from the PLC.

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.IO
Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

...

#End Region

    Private symbolLoader As TcAdsSymbolInfoLoader
    Private adsClient As TcAdsClient
    Private currentSymbol As ITcAdsSymbol = Nothing

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            adsClient = New TcAdsClient
            adsClient.Connect(801)
            symbolLoader = adsClient.CreateSymbolInfoLoader()
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub btnLoad_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLoad.Click
        Dim node As TreeNode
        treeViewSymbols.Nodes.Clear()
        Try
            If (Not cbFlat.Checked) Then
                Dim symbol As TcAdsSymbolInfo
                symbol = symbolLoader.GetFirstSymbol(True)
                While (Not symbol Is Nothing)
                    treeViewSymbols.Nodes.Add(CreateNewNode(symbol))
                    symbol = symbol.NextSymbol
                End While
            Else
                For Each symbol As TcAdsSymbolInfo In symbolLoader
                    node = New TreeNode(symbol.Name)
                    node.Tag = symbol
                    treeViewSymbols.Nodes.Add(node)
                Next
            End If
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub btnReadSymbolInfo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReadSymbolInfo.Click
        Dim symbol As ITcAdsSymbol
        Try
            symbol = adsClient.ReadSymbolInfo(tbSymbolname.Text)
            If (symbol Is Nothing) Then
                MessageBox.Show("Symbol " + tbSymbolname.Text + " not found")
                Return
            End If
            Call SetSymbolInfo(symbol)
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub btnFindSymbol_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFindSymbol.Click
        Dim symbol As ITcAdsSymbol
        Try
            symbol = symbolLoader.FindSymbol(tbSymbolname.Text)
            If (symbol Is Nothing) Then
                MessageBox.Show("Symbol " + tbSymbolname.Text + " not found")
                Return
            End If
            Call SetSymbolInfo(symbol)
        Catch err As Exception
            MessageBox.Show(err.Message)
        End Try
    End Sub

    Private Sub treeViewSymbols_AfterSelect(ByVal sender As System.Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles treeViewSymbols.AfterSelect
        If (e.Node.Text.Length > 0) Then
            If (TypeOf e.Node.Tag Is TcAdsSymbolInfo) Then
                SetSymbolInfo(DirectCast(e.Node.Tag, ITcAdsSymbol))
            End If
        End If
    End Sub

    Private Sub btnWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWrite.Click
        Try
            If (Not currentSymbol Is Nothing) Then
                adsClient.WriteSymbol(currentSymbol, tbValue.Text)
            End If
        Catch err As Exception
            MessageBox.Show("Unable to write Value. " + err.Message)
        End Try
    End Sub
    Private Function CreateNewNode(ByVal symbol As TcAdsSymbolInfo) As TreeNode
        Dim node As TreeNode
        Dim subsymbol As TcAdsSymbolInfo
        node = New TreeNode(symbol.Name)
        node.Tag = symbol
        subsymbol = symbol.FirstSubSymbol
        While (Not subsymbol Is Nothing)
            node.Nodes.Add(CreateNewNode(subsymbol))
            subsymbol = subsymbol.NextSymbol
        End While
        Return node
    End Function

    Private Sub SetSymbolInfo(ByVal symbol As ITcAdsSymbol)
        currentSymbol = symbol
        tbName.Text = symbol.Name.ToString()
        tbIndexGroup.Text = symbol.IndexGroup.ToString()
        tbIndexOffset.Text = symbol.IndexOffset.ToString()
        tbSize.Text = symbol.Size.ToString()
        tbDatatype.Text = symbol.Type
        tbDatatypeId.Text = symbol.Datatype.ToString()
        Try
            tbValue.Text = adsClient.ReadSymbol(symbol).ToString()
        Catch err As AdsDatatypeNotSupportedException
            tbValue.Text = err.Message
        Catch err As Exception
            MessageBox.Show("Unable to read Symbol Info. " + err.Message)
        End Try
    End Sub
End Class

PLC program

PROGRAM MAIN
VAR
VAR
    REAL32_1 AT %MB0 : REAL;		(* 1 *)
    REAL32_2 AT %MB4 : REAL;		(* 2 *)
    REAL32_3 AT %MB8 : REAL;		(* 3 *)
    REAL32_4 AT %MB12: REAL;		(* 4 *)
    REAL32_5 AT %MB16: REAL;		(* 5 *)
    REAL64_1 AT %MB20 : LREAL;		(* 6 *)
    REAL64_2 AT %MB28 : LREAL;		(* 7 *)
    REAL64_3 AT %MB36 : LREAL;		(* 8 *)
    REAL64_4 AT %MB44 : LREAL;		(* 9 *)
    REAL64_5 AT %MB52 : LREAL;		(* 10 *)
    INT32_1 AT %MB60 : DINT;		(* 11 *)
    INT32_2 AT %MB64 : DINT;		(* 12 *)
    INT32_3 AT %MB68 : DINT;		(* 13 *)
    INT32_4 AT %MB72 : DINT;		(* 14 *)
    INT32_5 AT %MB76 : DINT;		(* 15 *)
    UINT32_1 AT %MB80 : UDINT;		(* 16 *)
    UINT32_2 AT %MB84 : UDINT;		(* 17 *)
    UINT32_3 AT %MB88 : UDINT;		(* 18 *)
    UINT32_4 AT %MB92 : UDINT;		(* 19 *)
    UINT32_5 AT %MB96 : UDINT;		(* 20 *)
    INT16_1 AT %MB100 : INT;		(* 21 *)
    INT16_2 AT %MB102 : INT;		(* 22 *)
    INT16_3 AT %MB104 : INT;		(* 23 *)
    INT16_4 AT %MB106 : INT;		(* 24 *)
    INT16_5 AT %MB108 : INT;		(* 25 *)
    UINT16_1 AT %MB110 : UINT;		(* 26 *)
    UINT16_2 AT %MB112 : UINT;		(* 27 *)
    UINT16_3 AT %MB114 : UINT;		(* 28 *)
    UINT16_4 AT %MB116 : UINT;		(* 29 *)
    UINT16_5 AT %MB118 : UINT;		(* 30 *)
    INT8_1 AT %MB120 : SINT;		(* 31 *)
    INT8_2 AT %MB121 : SINT;		(* 32 *)
    INT8_3 AT %MB122 : SINT;		(* 33 *)
    INT8_4 AT %MB123 : SINT;		(* 34 *)
    INT8_5 AT %MB124 : SINT;		(* 35 *)
    UINT8_1 AT %MB125 : USINT;		(* 36 *)
    UINT8_2 AT %MB126 : USINT;		(* 37 *)
    UINT8_3 AT %MB128 : USINT;		(* 38 *)
    UINT8_4 AT %MB129 : USINT;		(* 39 *)
    UINT8_5 AT %MB130 : USINT;		(* 40 *)
    BOOL_1 AT %MX131.0 : BOOL;		(* 41 *)
    BOOL_2 AT %MX131.1 : BOOL;		(* 42 *)
    BOOL_3 AT %MX131.2 : BOOL;		(* 43 *)
    BOOL_4 AT %MX131.3 : BOOL;		(* 44 *)
    BOOL_5 AT %MX131.4 : BOOL;		(* 45 *)
    ARRAY_1	: ARRAY[1 .. 10] OF SINT;	(* 46 *)
    ARRAY_2	: ARRAY[1 .. 10] OF INT;	(* 47 *)
    ARRAY_3	: ARRAY[1 .. 10] OF DINT;	(* 48 *)
    ARRAY_4	: ARRAY[1 .. 10] OF LREAL;	(* 49 *)
    ARRAY_5	: ARRAY[1 .. 10] OF BOOL;	(* 50 *)
    STRING_1 : STRING(20);
END_VAR
;

Language / IDE | Unpack the sample program
Visual Basic (for .NET framework) | Sample06.exe


Visual Basic ADS.NET Sample07

TwinCAT Sample - VB - ADS .NET

Reading and writing of PLC variables of any type

From TwinCAT.Ads.NET version >= 1.0.0.15

Task

Read and Write variables of any type with the help of the ReadAny and WriteAny methods.

Description

ReadAny

In the event method btnRead_Click the method TcAdsClient.ReadAny is used to read a variable by handle:

TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type)
TwinCAT.Ads.TcAdsClient.ReadAny(System.Int32, System.Type, System.Int32[])

The type of the variable is passed to the method in the parameter type. In case the method was successfull, the read data will be returned as a object. The type of the object is equal to the type passed in the parameter type. Because some data types(arrays and strings) need additional information, an overload of the method ReadAny exists, that takes an additional parameter args. Full list of supported types can be found in the documentation of the overloaded method.

Example:

A global PLC variable of the type ARRAY[0..3] OF DINT should be read:

Dim hArr As Integer
Dim arr(0 To 3) As Integer

hArr = adsClient.CreateVariableHandle(".arr")
arr = adsClient.ReadAny(hArr, GetType(Integer()), New Integer() {4})

...
adsClient.DeleteVariableHandle(hArr)

WriteAny

In the event method btnWrite_Click the method TcAdsClient.WriteAny is used to write to a variable by handle:

TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object)
TwinCAT.Ads.TcAdsClient.WriteAny(System.Int32, System.Object, System.Int32[])

The parameter value is a reference to the object, that should be written to the PLC variable. Full list of supported types of the object value can be found in the documentation of the overloaded method.

Example:

A global PLC variable of the type ARRAY[0..3] OF DINT should be written:

Dim hArr As Integer
Dim arr As Integer() = New Integer() {1, 2, 3, 4}

hArr = adsClient.CreateVariableHandle(".arr")
adsClient.WriteAny(hArr, arr)

...
adsClient.DeleteVariableHandle(hArr)

Reading and writing of structures (not possible with the Compact Framework, CE):

To be able to read or write PLC structures the memory layout of the structure or class in .NET must be the same as in the PLC. The layout of a structure or class can be specified with the attribute StructLayoutAttribute. The LayoutKind must be set to LayoutKind.Sequential and the pack must be set to 1. Therefore the class SimpleStruct is defined as followed:

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class SimpleStruct
    Public lrealVal As Double
    Public dintVal1 As Integer
End Class

If arrays, strings or boolean values are define the class, one has to specify how these fields should be marshalled. This is accomplished with help of the MarshalAs attribute. Because arrays and strings do not have a fixed length in .NET, the property SizeConst is necessary for arrays and strings. It is not possible to marshal multidimensional arrays or arrays of structures with the :NET Framework 1.1. Multidimensional arrays in the PLC must be mapped to one dimensional arrays in .NET.

In the example the MarshalAsAttribute is used in the class ComplexStruct:

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class ComplexStruct
    Public intVal As Short
    'specifies how .NET should marshal the array
    'SizeConst specifies the number of elements the array has.
    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)> _
    Public dintArr(4) As Integer
    <MarshalAs(UnmanagedType.I1)> _
    Public boolVal As Boolean
    Public byteVal As Byte
    'specifies how .NET should marshal the string
    'SizeConst specifies the number of characters the string has.
    '(inclusive the terminating null ).
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)> _
    Public stringVal As String = ""
    Public simpleStruct1 As SimpleStruct = New SimpleStruct
End Class

Register ADS notifcations

In the event method btnAddNotifications_Click the method AddDeviceNotificationEx is used to register notifications for a PLC variable. If the value of a variable changes the event AdsNotificationEx is fired. The difference to the event AdsNotification, is that the value of the variable is stored in an object instead of in an AdsStream. Therefore one has to pass the type of the object to the method AddDeviceNotificationEx:

notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, GetType(Integer)))

As user object the textbox that should display the value is passed. If the event is fired, the event method adsClient_AdsNotificationEx is called.

Visual Basic (for .NET framework) program

Imports TwinCAT.Ads
Imports System.Runtime.InteropServices

Public Class Form1
    Inherits System.Windows.Forms.Form

    'PLC variable handles
    Private hdint1 As Integer
    Private hbool1 As Integer
    Private husint1 As Integer
    Private hlreal1 As Integer
    Private hstr1 As Integer
    Private hstr2 As Integer
    Private hcomplexStruct As Integer
    Private notificationHandles As ArrayList

#Region " Windows Form Designer generated code "

...

#End Region
    Private WithEvents adsClient As TcAdsClient

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        adsClient = New TcAdsClient
        notificationHandles = New ArrayList
        Try
            adsClient.Connect(801)
            btnDeleteNotifications.Enabled = False
            'create handles for the PLC variables
            hbool1 = adsClient.CreateVariableHandle("MAIN.bool1")
            hdint1 = adsClient.CreateVariableHandle("MAIN.dint1")
            husint1 = adsClient.CreateVariableHandle("MAIN.usint1")
            hlreal1 = adsClient.CreateVariableHandle("MAIN.lreal1")
            hstr1 = adsClient.CreateVariableHandle("MAIN.str1")
            hstr2 = adsClient.CreateVariableHandle("MAIN.str2")
            hcomplexStruct = adsClient.CreateVariableHandle("MAIN.ComplexStruct1")
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        adsClient.Dispose()
    End Sub

    Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click
        Try
            'read by handle
            'the second parameter specifies the type of the variable
            tbDint1.Text = adsClient.ReadAny(hdint1, GetType(Integer)).ToString()
            tbUsint1.Text = adsClient.ReadAny(husint1, GetType(Byte)).ToString()
            tbBool1.Text = adsClient.ReadAny(hbool1, GetType(Boolean)).ToString()
            tblreal1.Text = adsClient.ReadAny(hlreal1, GetType(Double)).ToString()
            'with strings one has to additionally pass the number of characters
            'specified in the PLC project(default 80).
            'This value is passed is an Integer array.
            tbStr1.Text = adsClient.ReadAny(hstr1, GetType(String), New Integer() {80}).ToString()
            tbStr2.Text = adsClient.ReadAny(hstr2, GetType(String), New Integer() {5}).ToString()
            FillStructControls(adsClient.ReadAny(hcomplexStruct, GetType(ComplexStruct)))
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

    Private Sub btnWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWrite.Click
        Try
            'write by handle
            'the second parameter is the object to be written to the PLC variable
            adsClient.WriteAny(hdint1, Integer.Parse(tbDint1.Text))
            adsClient.WriteAny(husint1, Byte.Parse(tbUsint1.Text))
            adsClient.WriteAny(hbool1, Boolean.Parse(tbBool1.Text))
            adsClient.WriteAny(hlreal1, Double.Parse(tblreal1.Text))
            'with strings one has to additionally pass the number of characters
            'the variable has in the PLC(default 80).
            adsClient.WriteAny(hstr1, tbStr1.Text, New Integer() {80})
            adsClient.WriteAny(hstr2, tbStr2.Text, New Integer() {5})
            adsClient.WriteAny(hcomplexStruct, GetStructFromControls())
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

    Private Sub btnAddNotifications_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAddNotifications.Click
        notificationHandles.Clear()
        Try
            'register notification
            notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.dint1", AdsTransMode.OnChange, 100, 0, tbDint1, GetType(Integer)))
            notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.usint1", AdsTransMode.OnChange, 100, 0, tbUsint1, GetType(Byte)))
            notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.bool1", AdsTransMode.OnChange, 100, 0, tbBool1, GetType(Boolean)))
            notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.lreal1", AdsTransMode.OnChange, 100, 0, tblreal1, GetType(Double)))
            notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str1", AdsTransMode.OnChange, 100, 0, tbStr1, GetType(String), New Integer() {80}))
            notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.str2", AdsTransMode.OnChange, 100, 0, tbStr2, GetType(String), New Integer() {5}))
            notificationHandles.Add(adsClient.AddDeviceNotificationEx("MAIN.complexStruct1", AdsTransMode.OnChange, 100, 0, tbDint1, GetType(ComplexStruct)))
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
        btnDeleteNotifications.Enabled = True
        btnAddNotifications.Enabled = False
    End Sub

    Private Sub btnDeleteNotifications_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDeleteNotifications.Click
        'delete registered notifications.
        Try
            For Each handle As Integer In notificationHandles
                adsClient.DeleteDeviceNotification(handle)
            Next
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
        notificationHandles.Clear()
        btnAddNotifications.Enabled = True
        btnDeleteNotifications.Enabled = False
    End Sub

    Private Sub adsClient_AdsNotificationEx(ByVal sender As Object, ByVal e As TwinCAT.Ads.AdsNotificationExEventArgs) Handles adsClient.AdsNotificationEx
        Dim txtBox As TextBox = e.UserData
        Dim type As Type = e.Value.GetType()
        If (type Is GetType(String) Or type.IsPrimitive()) Then
            txtBox.Text = e.Value
        ElseIf (type Is GetType(ComplexStruct)) Then
            FillStructControls(DirectCast(e.Value, ComplexStruct))
        End If
    End Sub

    Public Sub FillStructControls(ByVal struct As ComplexStruct)
        tbComplexStruct_IntVal.Text = struct.intVal
        tbComplexStruct_dintArr.Text = String.Format("{0}, {1}, {2}, {3}", struct.dintArr(0), struct.dintArr(1), struct.dintArr(2), struct.dintArr(3))
        tbComplexStruct_boolVal.Text = struct.boolVal
        tbComplexStruct_ByteVal.Text = struct.byteVal
        tbComplexStruct_stringVal.Text = struct.stringVal
        tbComplexStruct_SimpleStruct1_lrealVal.Text = struct.simpleStruct1.lrealVal
        tbComplexStruct_SimpleStruct_dintVal.Text = struct.simpleStruct1.dintVal1
    End Sub

    Public Function GetStructFromControls() As ComplexStruct
        Dim struct As ComplexStruct = New ComplexStruct
        Dim stringArr() As String = tbComplexStruct_dintArr.Text.Split(New Char() {","})

        struct.intVal = Short.Parse(tbComplexStruct_IntVal.Text)
        For i As Integer = 0 To stringArr.Length - 1
            struct.dintArr(i) = Integer.Parse(stringArr(i))
        Next
        struct.boolVal = Boolean.Parse(tbComplexStruct_boolVal.Text)
        struct.byteVal = Byte.Parse(tbComplexStruct_ByteVal.Text)
        struct.stringVal = tbComplexStruct_stringVal.Text
        struct.simpleStruct1.dintVal1 = Integer.Parse(tbComplexStruct_SimpleStruct_dintVal.Text)
        struct.simpleStruct1.lrealVal = Double.Parse(tbComplexStruct_SimpleStruct1_lrealVal.Text)
        Return struct
    End Function
End Class

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class SimpleStruct
    Public lrealVal As Double
    Public dintVal1 As Integer
End Class

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Class ComplexStruct
    Public intVal As Short
    'specifies how .NET should marshal the array
    'SizeConst specifies the number of elements the array has.
    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=4)> _
    Public dintArr(4) As Integer
    <MarshalAs(UnmanagedType.I1)> _
    Public boolVal As Boolean
    Public byteVal As Byte
    'specifies how .NET should marshal the string
    'SizeConst specifies the number of characters the string has.
    '(inclusive the terminating null ).
    <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)> _
    Public stringVal As String = ""
    Public simpleStruct1 As SimpleStruct = New SimpleStruct
End Class

PLC program

TYPE TSimpleStruct :
STRUCT
    lrealVal : LREAL := 1.23;
    dintVal : DINT := 120000;
END_STRUCT
END_TYPE

TYPE TComplexStruct :
STRUCT
    intVal : INT :=1200;
    dintArr : ARRAY[0..3] OF DINT:= 1,2,3,4;
    boolVal : BOOL := FALSE;
    byteVal : BYTE :=10;
    stringVal : STRING(5) := 'hallo';
    simpleStruct1 : TSimpleStruct;
END_STRUCT
END_TYPE

PROGRAM MAIN
VAR
    (*primitive Types*)
    bool1		:BOOL 	:= FALSE;
    int1		:INT 	:= 30000;
    dint1		:DINT	:=125000;
    usint1		:USINT	:=200;
    real1		:REAL	:= 1.2;
    lreal1		:LREAL	:=3.5;
    (*string Types*)
    str1		:STRING 	:= 'this is a test string';
    str2		:STRING(5):= 'hallo';
    (*struct Types*)
    complexStruct1 	: TComplexStruct;
END_VAR
;

Language / IDE | Unpack the sample program
Visual Basic (for .NET framework) | Sample07.exe


Visual Basic

TwinCAT ADS-Script-DLL - Reference

Reading and writing of PLC variables

The ADS-Script DLL does not make use of all Visual Basic's facilities. The ADS-OCX is more suitable if you want to access TwinCAT from Visual Basic, since it has been written to match Visual Basic. The particular value of the ADS-Script DLL is its use with script languages (VBScript and JScript). A Visual Basic program is included at this point, in order to demonstrate the facilities of the ADS-Script DLL more clearly.

First load the PLC program into the local PLC (run-time system 1) on your PC. Then start the Visual Basic program:

Unless you provide some other indication, a communication channel is created to your local PLC. If a different AdsAmsNetId and another port number are entered, you can create a connection to a different run-time system on another PC. The above-mentioned PLC program, however, must still be active on that other run-time system. It is also necessary that a remote computer, if selected, is entered into the TwinCAT System Control.

You can choose whether to read off the PLC variables by address (IndexGroup and IndexOffset) or by variable name. The same applies to writing new values. This is of no significance to the operator - it is just a matter of demonstrating the two possibilities.

The state of the PLC can be read off and altered in the upper right hand corner. A 5 corresponds to the RUN state, while a 6 corresponds to STOP.

Visual Basic 6 program

Option Explicit

Public TcClientSync As TCSCRIPTLib.TcScriptSync
Public nAdsAmsPort As Integer
Public strAdsAmsNetId As String

Private Sub Form_Load()
Set TcClientSync = CreateObject("TcScript.TcScriptSync")
Call TcClientSync.ConnectTo("", 801)
End Sub

Private Sub Form_Terminate()
Set TcClientSync = Nothing
End Sub

Private Sub cmdConnect_Click()
nAdsAmsPort = CLng(txtAdsAmsPort.Text)
if (Len(txtAdsAmsNetId.Text) > 0) Then
strAdsAmsNetId = txtAdsAmsNetId.Text
Else
strAdsAmsNetId = ""
End If
Set TcClientSync = CreateObject("TcScript.TcScriptSync")
Call TcClientSync.ConnectTo(strAdsAmsNetId, nAdsAmsPort)
End Sub

Private Sub cmdRead_Click()
Dim VarBool As Variant
Dim VarInt8 As Byte
Dim VarInt16 As Integer
Dim VarInt32 As Long
Dim VarReal32 As Single
Dim VarReal64 As Double

VarBool = TcClientSync.ReadBool(&H4020, 0)
lblBoolean.Caption = VarBool

VarInt8 = TcClientSync.ReadInt8(&H4020, 2)
lblInt8.Caption = VarInt8

VarInt16 = TcClientSync.ReadInt16(&H4020, 4)
lblInt16.Caption = VarInt16

VarInt32 = TcClientSync.ReadInt32(&H4020, 6)
lblInt32.Caption = VarInt32

VarReal32 = TcClientSync.ReadReal32(&H4020, 10)
lblReal32.Caption = VarReal32

VarReal64 = TcClientSync.ReadReal64(&H4020, 14)
lblReal64.Caption = VarReal64
End Sub

Private Sub cmdReadByName_Click()
Dim VarBool As Variant
Dim VarInt8 As Byte
Dim VarInt16 As Integer
Dim VarInt32 As Long
Dim VarReal32 As Single
Dim VarReal64 As Double

VarBool = TcClientSync.ReadBoolVar(".PLCBoolVar")
lblBoolean.Caption = VarBool

VarInt8 = TcClientSync.ReadInt8Var(".PLCSIntVar")
lblInt8.Caption = VarInt8

VarInt16 = TcClientSync.ReadInt16Var(".PLCIntVar")
lblInt16.Caption = VarInt16

VarInt32 = TcClientSync.ReadInt32Var(".PLCDIntVar")
lblInt32.Caption = VarInt32

VarReal32 = TcClientSync.ReadReal32Var(".PLCRealVar")
lblReal32.Caption = VarReal32

VarReal64 = TcClientSync.ReadReal64Var(".PLCLRealVar")
lblReal64.Caption = VarReal64
End Sub

Private Sub cmdWrite_Click()
Dim VarBool As Boolean
Dim VarInt8 As Byte
Dim VarInt16 As Integer
Dim VarInt32 As Long
Dim VarReal32 As Single
Dim VarReal64 As Double

If (txtBoolean.Text <> "") Then
VarBool = CBool(txtBoolean.Text)
Call TcClientSync.WriteBool(&H4020,
0, VarBool)
txtBoolean.Text = VarBool
End If

If (txtInt8.Text <> "") Then
VarInt8 = CByte(txtInt8.Text)
Call
TcClientSync.WriteInt8(&H4020, 2, VarInt8)
txtInt8.Text = VarInt8
End If

If (txtInt16.Text <> "") Then
VarInt16 = CInt(txtInt16.Text)
Call
TcClientSync.WriteInt16(&H4020, 4, VarInt16)
txtInt16.Text = VarInt16
End If

If (txtInt32.Text <> "") Then
VarInt32 = CLng(txtInt32.Text)
Call
TcClientSync.WriteInt32(&H4020, 6, VarInt32)
txtInt32.Text = VarInt32
End If

If (txtReal32.Text <> "") Then
VarReal32 = CSng(txtReal32.Text)
Call
TcClientSync.WriteReal32(&H4020, 10, VarReal32)
txtReal32.Text = VarReal32
End If

If (txtReal64.Text <> "") Then
VarReal64 = CDbl(txtReal64.Text)
Call
TcClientSync.WriteReal64(&H4020, 14, VarReal64)
txtReal64.Text = VarReal64
End If
End Sub

Private Sub cmdWriteByName_Click()
Dim VarBool As Boolean
Dim VarInt8 As Byte
Dim VarInt16 As Integer
Dim VarInt32 As Long
Dim VarReal32 As Single
Dim VarReal64 As Double

If (txtBoolean.Text <> "") Then
VarBool = CBool(txtBoolean.Text)
Call
TcClientSync.WriteBoolVar(".PLCBoolVar", VarBool)
txtBoolean.Text = VarBool
End If

If (txtInt8.Text <> "") Then
VarInt8 = CByte(txtInt8.Text)
Call
TcClientSync.WriteInt8Var(".PLCSIntVar", VarInt8)
txtInt8.Text = VarInt8
End If

If (txtInt16.Text <> "") Then
VarInt16 = CInt(txtInt16.Text)
Call
TcClientSync.WriteInt16Var(".PLCIntVar", VarInt16)
txtInt16.Text = VarInt16
End If

If (txtInt32.Text <> "") Then
VarInt32 = CLng(txtInt32.Text)
Call
TcClientSync.WriteInt32Var(".PLCDIntVar", VarInt32)
txtInt32.Text = VarInt32
End If

If (txtReal32.Text <> "") Then
VarReal32 = CSng(txtReal32.Text)
Call
TcClientSync.WriteReal32Var(".PLCRealVar", VarReal32)
txtReal32.Text = VarReal32
End If

If (txtReal64.Text <> "") Then
VarReal64 = CDbl(txtReal64.Text)
Call
TcClientSync.WriteReal64Var(".PLCLRealVar", VarReal64)
txtReal64.Text = VarReal64
End If
End Sub

Private Sub cmdReadState_Click()
lblAdsState.Caption = TcClientSync.ReadAdsState()
End Sub

Private Sub cmdWriteState_Click()
If (txtAdsState.Text <> "") Then
If (txtAdsState.Text <>
TcClientSync.ReadAdsState()) Then
Call
TcClientSync.WriteAdsState(txtAdsState.Text)
End If
End If
End Sub

PLC program

VAR_GLOBAL
    PLCBoolVar	AT %MX0.0: BOOL := TRUE;
    PLCSIntVar	AT %MB2	: USINT := 75;
    PLCIntVar	AT %MB4	: UINT := 12423;
    PLCDIntVar	AT %MB6	: UDINT := 43234532;
    PLCRealVar	AT %MB10	: REAL := 3.1415;
    PLCLRealVar	AT %MB14	: LREAL := 3.141592654;
END_VAR

Language / IDE | Unpack the sample program
Visual Basic 6 | ADS-Script-DLL Sample02.exe