Module 4 - Introduction To ADS-Programming
Module 4 - Introduction To ADS-Programming
2009 Beckhoff Automation BVBA This document is not to be replicated in part or in Whole, either electronically, by copy machine, Photographically, or by scanning without the written consent of Beckhoff Belgium.
Version: 19052012
3 This manual assumes that TwinCAT is already installed as well Visual Studio. This manual also assumes that the reader has a basic level of knowledge of .NET-programming.
Contents
1. ADS concept .......................................................................................................................................................... 5 1.1. ADS Architecture ............................................................................................................................................ 5 1.2. ADS-PORT ...................................................................................................................................................... 6 1.3. ADS-AmsNetId ............................................................................................................................................... 7 2. Setting up communication between devices .......................................................................................................... 8 2.1. Setting up the route between the ADS-Devices .............................................................................................. 8 Local connection: ................................................................................................................................................... 8 Remote connection: ................................................................................................................................................ 8 2.2. Linking into Microsoft Visual Studio .NET .................................................................................................. 10 2.3. Setting up communication between devices .............................................................................................. 11 3. Receiving and sending data .................................................................................................................................. 12 3.1. IndexGroup and IndexOffset ......................................................................................................................... 12 3.2. AdsStream ..................................................................................................................................................... 13 3.3. VariableHandle .............................................................................................................................................. 13 3.4. Variable Types ............................................................................................................................................... 13 3.5. Read/Write Methods by Indexgroup and IndexOffset .................................................................................. 14 By Object .............................................................................................................................................................. 14 By AdsStream ...................................................................................................................................................... 17 3.6. Read/Write Methods by Variable Handle ..................................................................................................... 22 By Object .............................................................................................................................................................. 23 By AdsStream ...................................................................................................................................................... 23 3.7. Update methods by notification ..................................................................................................................... 24 Impact of CycleTime & MaxDelayTime ............................................................................................................. 24 By Object .............................................................................................................................................................. 25 By AdsStream ...................................................................................................................................................... 30 3.8. ADS-Sum Command: Reading or writing several variables ......................................................................... 35 Background: ......................................................................................................................................................... 35 Version of Target device: ..................................................................................................................................... 35 Bytes length of requested data: ............................................................................................................................ 35 Number of Sub-ADS calls: .................................................................................................................................. 36 3.9. SymbolInfo: ................................................................................................................................................... 42 A single variable ................................................................................................................................................... 42 All variables ......................................................................................................................................................... 45 4. Appendix .............................................................................................................................................................. 47 4.1. System manager Task .................................................................................................................................... 47 Creating a user task .............................................................................................................................................. 47 Creating the variables ........................................................................................................................................... 49 Receiving and sending data .................................................................................................................................. 50 4.2. Debugging CE project form VS2005 and VS2008 ....................................................................................... 51 Copy the Windows CE debug files to the device ................................................................................................. 51 CoreConOverrideSecurity Registry Key .............................................................................................................. 52 Run ConmanClient2.exe on the Device ............................................................................................................... 52 Configure Visual Studio Options ......................................................................................................................... 52 Deploy and debug the application ........................................................................................................................ 53 4.3. Creating setup files for Windows CE using Visual Studio 2005 .................................................................. 54 Packaging the CAB files ...................................................................................................................................... 54 4.4. Marshaling ..................................................................................................................................................... 55
For more elaborate info, please take a look into the information System: http://infosys.beckhoff.com/ This page kept intentionally blank
1. ADS concept
1.1. ADS Architecture
The TwinCAT system architecture allows the individual modules of the software (e.g. TwinCAT PLC, TwinCAT NC, ...) to be treated as independent devices: For every task there is a software module ("Server" or "Client"). The servers in the system are the executing working "devices" in the form of software, whose operating behaviour is exactly like that of a hardware device. For this reason we can speak of "virtual" devices implemented in the software. The "clients" are programs which request the services of the "servers", e.g. a visualisation, or even a "programming device" in the form of a program. It is thus possible for TwinCAT to grow, since there can always be new servers and clients for tasks such as camshaft controllers, oscilloscopes, PID controllers etc.. The messages between these objects are exchanged through a consistent ADS (Automation Device Specification) interface by the "message router". This manages and distributes all the messages in the system and over the TCP/IP connections. TwinCAT message routers exist on every TwinCAT PC and on every Beckhoff BCxxxx Bus Controller (e.g. BC3100, BC8100, ...). In this way PC systems can be linked (via TCP/IP) as CAN Bus Controllers (through serial interfaces and fieldbuses). This allows all TwinCAT server and client programs to exchange commands and data, to send messages, transfer status information, etc.. The following diagram shows the TwinCAT device concept and seems complicated and will be discussed in later modules but just realize that the TwinCAT system is your computer. Message router talks to and through the TCP/IP port to the remote system (ADS interface):
1.2.
ADS-PORT
The ADS devices in a TwinCAT message router are uniquely identified by a number referred to as the ADS-PortNr. For ADS devices this has a fixed specification, whereas pure ADS client applications (e.g. a visualisation system) are allocated a variable ADS port number when they first access the message router.
ADS-PortNr 100 110 300 301 302 500 800 801 811 821 831 900 950 10000 27110 ADS device description Logger (Only NT-Log) EventLogger IO Additional Task 1 Additional Task 2 NC PLC Runtime System (Only at the buscontroller) PLC Runtime System1 PLC Runtime System2 PLC Runtime System3 PLC Runtime System4 CamShaft Controller CamTool Server System Service Scope server Constants port numbers AMSPORT_LOGGER AMSPORT_EVENTLOG AMSPORT_R0_IO
AMSPORT_R0_NC AMSPORT_R0_PLC AMSPORT_R0_PLC_RTS1 AMSPORT_R0_PLC_RTS2 AMSPORT_R0_PLC_RTS3 AMSPORT_R0_PLC_RTS4 AMSPORT_R0_CAM AMSPORT_R0_CAMTOOL AMSPORT_R3_SYSSERV AMSPORT_R3_SCOPESERVER
1.3.
ADS-AmsNetId
It is not only possible to exchange data between TwinCAT modules on one PC, it is even possible to do so by ADS methods between multiple TwinCAT PC's on the network.
Configuration: The ADS-AmsNetId of a TwinCAT-PC can be set in the TwinCAT system service: A menu appears after the system service icon has been selected. By selecting "Properties" you reach the "TwinCAT System Properties" dialogue.
After the "AMS router" page has been selected, the desired identifier can be read or modified in the "AMS Net Id" field. By default, the TwinCAT installation constructs the AMSNetId from the TCP/IP address of the PC with an extension of ".1.1" (as if it were a "subnet mask" for field busses, target bus controllers,..). The AMSNetId can be chosen freely, one restriction on this matter is that all the AMSNetIds need uniquely be defined in the network. When the services of an ADS device in the network are called on, its message router, or, more precisely, its AMSNetId, must be known. This is achieved through insertion of the target PC, as a result of which TwinCAT can establish the connection between the TCP/IP address of the target PC and the AMSNetId of the target message router address.
Remote connection:
The main element in remote connection is a system called a router. As the name implies, the router, routes the messages to and from all remote devices to a single controller and manages them all. Remote connectivity involves ADS or Automation Device Specification. This is an interface that enables communication between ADS compliant devices and systems over TCP/IP.
Establishing a Route The connection between a computer and a remote system is called a route. To achieve a communication link between the systems, a route needs to be established. This can be done through the use of the system manager. Choose System configuration on the left side tree. Click in the right side part of the screen on General if not already selected- and click on the Choose Target Button. The choose Target system screen will pop-up and shows all available systems. The First time this is opened, only the local system will be displayed.
9 The Add route Dialog screen will pop-up. In this window click on the broadcast search button to begin discovering all connected systems. The discovered systems will be displayed. On a LAN, the system will list every TwinCAT-enabled device which is in Config or Run mode. Select the remote system you wish to add, and click the Add Route button. In the login information screen that appears, enter the username and password for the remote system. And Click the OK button. The remote system should now show an X in the connected column. This makes it easy to find connected systems when numerous systems are visible, such as when the systems are connected to the LAN. Click the Close button. The Remote system should now be displayed in the Choose target system screen. Select the remote system and click the OK button to return to the System manager. The system manager will now display extra tabs related to the remote system. The remote system that is now connected is identified in the lower right corner in red.
10
2.2.
In this dialog you have to press the Browse button and select the file TwinCAT.Ads.dll.
C#: In the Solution Explorer, you can check if the component has been successfully added to the list of references. VB.NET: In the Solution Explorer, double click on MyProject, on the next view select Tab References for the list of references
11
2.3.
Parameters: netID: NetId of the ADS server. srvPort: Port number of the ADS server.
C#:
using TwinCAT.Ads; namespace TRAINING_ADS_CNET { public partial class Form1 : Form { TcAdsClient TcClient; /* unused code snipped out */ private void Form1_Load(object sender, EventArgs e) { TcClient = new TcAdsClient(); TcClient.Connect("127.255.255.1.1.1", 801); } } }
VB.NET:
Imports TwinCAT.Ads Public Class Form1 Dim TcClient As TcAdsClient Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load TcClient = New TcAdsClient() TcClient.Connect(801) End Sub End Class
12
3.1.
= =
The index group distinguishes different data within a port Indicates the offset, the byte from which reading or writing is to start.
16#F021
16#F030
16#F031
For more elaborate list on Index-groups/-offsets, please refer to the Information System The TwinCAT.Ads.dll has and enumeration of the mostly used index-groups and offsets, for this have a look at "AdsReservedIndexGroups and AdsReservedIndexOffsets.
13
3.2.
AdsStream
The class AdsStream is a stream class used for Ads communication. Derived from the System.IO.Memorystream, which is a stream whose backing store is memory. A stream is an abstraction of a sequence of bytes, such as a file, an input/output device, an interprocess communication pipe, or a TCP/IP socket.
3.3.
VariableHandle
A unique handle for an ADS variable.
3.4.
Variable Types
The variable types from within the PLC-program would map into:
System Manager IEC61131-3 BIT BIT8 BITARR8 BITARR16 BITARR32 INT8 INT16 INT32 INT64 UINT8 UINT16 UINT32 UINT64 FLOAT DOUBLE BOOL BOOL BYTE WORD DWORD SINT INT DINT LINT USINT UINT UDINT ULINT REAL LREAL Correspondent .NET Visual Basic C# Keyword type Keyword System.Boolean System.Boolean System.Byte System.UInt16 System.UInt32 System.SByte System.Int16 System.Int32 System.Int64 System.Byte System.UInt16 System.UInt32 System.UInt64 System.Single System.Double bool bool byte ushort uint sbyte short int long byte ushort uint ulong float double Boolean Boolean Byte Short Integer Long Byte Single Double
14
3.5.
By Object
Read data synchronously from an ADS device and writes it to an object. If the Type of the object to be read is a string type, the first element of the parameter args specifies the number of characters of the string. If the Type of the object to be read is an array type, the number of elements for each dimension has to be specified in the parameter args. Only 1 dimensional Arrays are supported.
public object ReadAny(long indexGroup, long indexOffset, Type type); public object ReadAny(long indexGroup, long indexOffset, Type type, int[] args);
The write counterparts: public void WriteAny(long indexGroup, long indexOffset, object value); public void WriteAny(long indexGroup, long indexOffset, object value, int[] args);
VB.NET:
Imports TwinCAT.Ads Imports System.Type Public Class Form1 /*Unused code snipped out*/ Private Sub BtnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnRead.Click LblReadAny.Text = TcClient.ReadAny(&H4021, 80, GetType(Boolean)) End Sub End Class
16 So for MB12, type INT in our PLC program would give us type INT16 in .NET C#:
Using TwinCAT.Ads; Using System; public partial class Form1 : Form { private void btnReadAny_Click(object sender, EventArgs e) { lblReadAny.Text = TcClient.ReadAny(0x4020, 12, typeof(Int16)).ToString(); } }
VB.NET:
Imports TwinCAT.Ads Imports System.Type Public Class Form1 Private Sub BtnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnRead.Click LblReadAny.Text = TcClient.ReadAny(&H4021, 80, GetType(Int16)) End Sub End Class
VB.NET:
Imports TwinCAT.Ads Imports System.Type Public Class Form1 Private Sub btnWriteAny_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWriteAny.Click TcClient.WriteAny(&H4020, 14, Int16.Parse(txtBoxWriteAny.Text)) End Sub End Class
17
By AdsStream
Reads data synchronously from an ADS device and writes it to the given stream. Each read method returns the number of successfully data bytes being read.
public int Read(int indexGroup, int indexOffset, AdsStream dataStream); public int Read(long indexGroup, long indexOffset, AdsStream dataStream); public int Read(long indexGroup, long indexOffset, AdsStream dataStream, int offset, int length);
Write counterparts:
public void Write(int indexGroup, int indexOffset, AdsStream dataStream); public void Write(int indexGroup, int indexOffset, AdsStream dataStream, int offset, int length); public void Write(long indexGroup, long indexOffset, AdsStream dataStream); public void Write(long indexGroup, long indexOffset, AdsStream dataStream, int offset, int length);
As for our last example WRITING diVar0, this would give us: C#:
using System.IO; using TwinCAT.Ads; public partial class Form1 : Form { TcAdsClient client = new TcAdsClient(); int hVar; public Form1() { InitializeComponent(); client.Connect(801); } private void BtnWrite_Click(object sender, EventArgs e) { AdsStream dataStream = new AdsStream(); BinaryWriter binWrite = new BinaryWriter(dataStream); dataStream.Position = 0; binWrite.Write(Int32.Parse(TxtbxWrite.Text)); client.Write(0x4020, 14, dataStream); } }
18 VB.NET:
Imports System.IO Imports TwinCAT.Ads Public Class Form1 Dim client As New TcAdsClient Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load client.Connect(801) End Sub Private Sub BtnWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnWrite.Click Dim dataStream As New AdsStream Dim binWrite As New BinaryWriter(dataStream) dataStream.Position = 0 binWrite.Write(Integer.Parse(TxtBoxWrite.Text)) client.Write(&H4020, 14, dataStream) End Sub End Class
19 Reading out the array VisuPositions of type LREAL, this would give us: C#:
using System.IO; using TwinCAT.Ads; public partial class Form1 : Form { TcAdsClient client = new TcAdsClient(); int hVar; public Form1() { InitializeComponent(); client.Connect(801); } private void btnRead_Click(object sender, EventArgs e) { AdsStream dataStream = new AdsStream(20 * 8); BinaryReader binRead = new BinaryReader(dataStream); client.Read(0x4040, 38, dataStream); TxtBoxRead1.Text = binRead.ReadDouble().ToString(); TxtBoxRead2.Text = binRead.ReadDouble().ToString(); //. redundant code snipped out TxtBoxRead19.Text = binRead.ReadDouble().ToString(); TxtBoxRead20.Text = binRead.ReadDouble().ToString(); } }
VB.NET:
Imports System.IO Imports TwinCAT.Ads Public Class Form1 Dim client As New TcAdsClient Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load client.Connect(801) End Sub Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click Dim dataStream As New AdsStream(20 * 8) Dim binRead As New BinaryReader(dataStream) client.Read(&H4040, 38, dataStream) TxtBoxRead1.Text = binRead.ReadDouble() TxtBoxRead2.Text = binRead.ReadDouble() '. Redundant code snipped out TxtBoxRead19.Text = binRead.ReadDouble() TxtBoxRead20.Text = binRead.ReadDouble() End SubEnd Class
20 PLC-example NC-struct In the information System we find that we can read on Indexgrp 0x4000 + AxisID offset 0 the axis parameter from a configured axis.
AdsBinaryReader derives from BinaryReader and reads primitive as well as PLC data types as binary values. ReadPlcDATE ReadPlcString ReadPlcTIME Reads a PLC Date type from the current stream. Reads a PLC string from the current stream. Reads a PLC Time type from the current stream.
22
3.6.
PLC-example Global Variables: BIT0 AT%MX10.0 BIT1 AT%MX10.1 BIT2 AT%MX10.2 BIT3 AT%MX10.3 iVar0 AT%MB12 diVar0 AT%MB14 VisuPositions C#:
Using TwinCAT.Ads; Using System; public partial class Form1 : Form { /* unused code snipped out */ int varHandle = TcClient.CreateVariableHandle(".BIT0"); /* unused code snipped out */ }
: : : : : : :
VB.NET:
Imports TwinCAT.Ads Imports System.Type Public Class Form1 /* unused code snipped out */ Dim varHandle As Integer = TcClient.CreateVariableHandle(".BIT0") /* unused code snipped out */ End Class
23
By Object
public object ReadAny(int variableHandle, Type type); public object ReadAny(int variableHandle, Type type, int[] args); public void WriteAny(int variableHandle, object value); public void WriteAny(int variableHandle, object value, int[] args);
By AdsStream
public int Read(int variableHandle, AdsStream dataStream); public int Read(int variableHandle, AdsStream dataStream, int offset, int length); public void Write(int variableHandle, AdsStream dataStream); public void Write(int variableHandle, AdsStream dataStream, int offset, int length);
24
3.7.
25 PLC-example Global Variables: BIT0 AT%MX10.0 BIT1 AT%MX10.1 BIT2 AT%MX10.2 BIT3 AT%MX10.3 iVar0 AT%MB12 diVar0 AT%MB14 VisuPositions : : : : : : : BOOL; BOOL; BOOL; BOOL; INT; DINT; ARRAY [0..19] OF LREAL;
By Object
public int AddDeviceNotificationEx(long indexGroup, long indexOffset, AdsTransMode transMode, int cycleTime, int maxDelay, object userData, Type type); public int AddDeviceNotificationEx(long indexGroup, long indexOffset, AdsTransMode transMode, int cycleTime, int maxDelay, object userData, Type type, int[] args); public int AddDeviceNotificationEx(string variableName, AdsTransMode transMode, int cycleTime, int maxDelay, object userData, Type type); public int AddDeviceNotificationEx(string variableName, AdsTransMode transMode, int cycleTime, int maxDelay, object userData, Type type, int[] args);
TransMode Specifies if the event should be fired cyclically or only if the variable has changed. CycleTime The ADS server checks whether the variable has changed after this time interval. The unit is in ms. maxDelay The AdsNotification event is fired at the latest when this time has elapsed. The unit is ms. userData This object can be used to store user specific data. type Type of the object stored in the event argument. args Additional arguments. The event handler receives a AdsNotificationExEventArgs containing data related to the AdsNotificationEx event. The following AdsNotificationExEventArgs properties provide information specific to this event: Property NotificationHandle TimeStamp UserData Value Description Gets the handle of the connection. Gets the timestamp. Gets the user object. Value of the ads variable.
26 For the BIT3-variable in our PLC-example as global variable this would give us C#:
using TwinCAT.Ads; namespace Notif { public partial class Form1 : Form { TcAdsClient client = new TcAdsClient(); int notifHandle; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { client.Connect(801); client.AdsNotificationEx += new AdsNotificationExEventHandler(client_AdsNotificationEx); notifHandle = client.AddDeviceNotificationEx( ".Bit3", AdsTransMode.OnChange, 100, 100, TxtBoxValue, typeof(bool)); } void client_AdsNotificationEx( object sender, AdsNotificationExEventArgs e) { if (e.NotificationHandle == notifHandle) { //TextBox txtbx = (TextBox)e.UserData; TextBox txtbx = e.UserData as TextBox; if (txtbx != null) { txtbx.Text = e.Value.ToString(); } } } } }
With TxtBoxValue being our TextBox added to the form. While creating the notificationHandle, we pass the TextBox instance as an object to the handler.
27 VB.NET:
Imports TwinCAT.Ads Partial Public Class Form1 Inherits Form Private client As New TcAdsClient() Private notifHandle As Integer Private Sub Form1_Load(sender As Object, e As EventArgs) client.Connect(801) AddHandler client.AdsNotificationEx, AddressOf client_AdsNotificationEx notifHandle = client.AddDeviceNotificationEx( ".Bit3", AdsTransMode.OnChange, 100, 100, TxtBoxValue, GetType(Boolean)) End Sub Private Sub client_AdsNotificationEx( sender As Object, e As AdsNotificationExEventArgs) If e.NotificationHandle = notifHandle Then Dim txtbx As TextBox = TryCast(e.UserData, TextBox) If txtbx IsNot Nothing Then txtbx.Text = e.Value.ToString() End If End If End Sub End Class
Taken from the MSDN website only valid for VB.Net: If an attempted conversion fails, CType and DirectCast both throw an InvalidCastException error. This can adversely affect the performance of your application. TryCast returns Nothing (Visual Basic), so that instead of having to handle a possible exception, you need only test the returned result against Nothing.
28 For the visupositions-variable in our PLC-example as global variable this would give us C#:
using TwinCAT.Ads; namespace Notif { public partial class Form1 : Form { TcAdsClient client = new TcAdsClient(); int notifHandleArray; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { client.Connect(801); client.AdsNotificationEx += new AdsNotificationExEventHandler(client_AdsNotificationEx); notifHandleArray = client.AddDeviceNotificationEx( ".VisuPositions", AdsTransMode.OnChange, 100, 100, null, typeof(double[]), new int[] { 20 }); } void client_AdsNotificationEx( object sender, AdsNotificationExEventArgs e) { if (e.NotificationHandle == notifHandleArray) { double[] array = (double[])e.Value; } } } }
29 VB.NET:
Imports TwinCAT.Ads Public Class Form1 Dim TcClient As TcAdsClient Dim notifHandleArray As Int32 Dim array() As Double Private Sub Form1_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load TcClient = New TcAdsClient() TcClient.Connect(801) AddHandler TcClient.AdsNotificationEx, AddressOf client_adsNotification notifHandleArray = TcClient.AddDeviceNotificationEx( ".VisuPositions", AdsTransMode.OnChange, 100, 100, Nothing, GetType(Double()), New Int32() {20}) End Sub Protected Sub client_adsNotification( ByVal sender As Object, ByVal e As AdsNotificationExEventArgs) If notifHandleArray.Equals(e.NotificationHandle) Then array = e.Value End If End Sub End Class
30
By AdsStream
public int AddDeviceNotification(int indexGroup, int indexOffset, AdsStream dataStream, int offset, int length, AdsTransMode transMode, int cycleTime, int maxDelay, object userData); public int AddDeviceNotification(int indexGroup, int indexOffset, AdsStream dataStream, AdsTransMode transMode, int cycleTime, int maxDelay, object userData); public int AddDeviceNotification(long indexGroup, long indexOffset, AdsStream dataStream, int offset, int length, AdsTransMode transMode, int cycleTime, int maxDelay, object userData); public int AddDeviceNotification(long indexGroup, long indexOffset, AdsStream dataStream, AdsTransMode transMode, int cycleTime, int maxDelay, object userData); public int AddDeviceNotification(string variableName, AdsStream dataStream, int offset, int length, AdsTransMode transMode, int cycleTime, int maxDelay, object userData); public int AddDeviceNotification(string variableName, AdsStream dataStream, AdsTransMode transMode, int cycleTime, int maxDelay, object userData);
TransMode Specifies if the event should be fired cyclically or only if the variable has changed. CycleTime The ADS server checks whether the variable has changed after this time interval. The unit is in ms. maxDelay The AdsNotification event is fired at the latest when this time has elapsed. The unit is ms. userData This object can be used to store user specific data. The event handler receives a AdsNotificationEventArgs containing data related to the AdsNotification event. The following AdsNotificationEventArgs properties provide information specific to this event: Property DataStream Length NotificationHandle Offset TimeStamp UserData Description Streams that holds the notification data. Gets the Length of the data in the stream. Gets the handle of the connection. Gets the Offset of the data in the stream. Gets the timestamp. Gets the user object. This object is passed by to AddDeviceNotification and can be used to store data.
With strAxisState defined as TYPE strAxisstate : STRUCT AxisName Ready bError actVelocity actPosition setVelocity setPosition Lagdistance END_STRUCT END_TYPE
: : : : : : : :
using TwinCAT.Ads; namespace TRAINING_ADS_CNET { public partial class Form1 : Form { public struct AxisStruct { public string AxisName; public bool Ready; public bool Error; public double ActVelocity; public double ActPosition; public double SetVelocity; public double SetPosition; public double LagDistance; } TcAdsClient TcClient; AdsStream AdsStrAxisStruct; int notifHandle; AxisStruct axisStruct1 = new AxisStruct(); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { TcClient = new TcAdsClient(); TcClient.Connect("192.168.1.105.1.1", 801); TcClient.AdsNotification += new AdsNotificationEventHandler(TcClient_AdsNotification); AdsStrAxisStruct = new AdsStream(62); notifHandle = TcClient.AddDeviceNotification(".Axisstate1",AdsStrAxisStruct , AdsTransMode.OnChange,100,0,null); } } }
33 VB.NET:
Imports TwinCAT.Ads Imports System.Type Public Structure AxisStruct Dim AxisName As String Dim Ready As Boolean Dim bError As Boolean Dim actVelocity As Double Dim actPosition As Double Dim setVelocity As Double Dim setPosition As Double Dim Lagdistance As Double End Structure Public Class Form1 Dim TcClient As TcAdsClient Dim AdsStrAxisStruct As AdsStream Dim notifHandle As Int32 Dim axisStruct1 As New AxisStruct() Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load TcClient = New TcAdsClient() TcClient.Connect(801) Dim AdsStrAxisStruct As New AdsStream(62) AddHandler TcClient.AdsNotification, AddressOf client_adsNotification notifHandle = TcClient.AddDeviceNotification(".Axisstate1", AdsStrAxisStruct, AdsTransMode.OnChange, 100, 0, Nothing) End Sub
VB.NET:
Protected Sub client_adsNotification(ByVal sender As Object, ByVal e As AdsNotificationEventArgs) If e.NotificationHandle = notifHandle Then e.DataStream.Position = 0 Dim adsbinreader As AdsBinaryReader = New AdsBinaryReader(e.DataStream) axisStruct1.AxisName = adsbinreader.ReadPlcString(20) axisStruct1.Ready = adsbinreader.ReadBoolean() axisStruct1.bError = adsbinreader.ReadBoolean() axisStruct1.actVelocity = adsbinreader.ReadDouble() axisStruct1.actPosition = adsbinreader.ReadDouble() axisStruct1.setVelocity = adsbinreader.ReadDouble() axisStruct1.setPosition = adsbinreader.ReadDouble() axisStruct1.Lagdistance = adsbinreader.ReadDouble() End If End Sub
35
3.8.
Using the ADS Sum Command it is possible to read or write several variables in one command. Designed as TcAdsClient.ReadWrite it is used as a container, which transports all sub-commands in one ADS stream.
Background:
ADS offer powerful and fast communication to exchange any kind of information. It's possible to read single variables or complete arrays and structures with each one single ADS-API call. This new ADS command offers to read with one single ADS call multiple variables which are not structured within a linear memory. As a result the ADS caller application (like scada Systems etc.) can extremely speed up cyclic polling. Sample:
Until now : Polling 4000 single variables which are not in a linear area (like array / structure / fixed PLC address ) would cause 4000 single Ads-ReadReq with each 1-2 ms protocol time. As a result the scanning of these variables takes 4000ms-8000ms. New Ads-Command allows to read multiple variables with one single ADS-ReadReq : 4000 single variables are handled with e.g. 8 single Ads-ReadReq (each call requesting 500 variables) with each 1-2 ms protocol time. As a result the scanning of these variables takes just few 10ms.
36
: : : : :
using TwinCAT.Ads; using System.IO; namespace WindowsApplication1 { public partial class Form1 : Form { // Structure declaration for handles internal struct VariableInfo { public int indexGroup; public int indexOffset; public int length; } TcAdsClient tcClient = new TcAdsClient(); private string[] variableNames; private int[] variableLengths; VariableInfo[] variables;
37
public Form1() { InitializeComponent(); tcClient.Connect(801); //Fill structures with name and size of PLC variables variableNames = new string[] { ".VAR1", ".VAR2", ".VAR3", ".VAR4", ".VAR5" }; variableLengths = new int[] { 2, 2, 2, 4, 2 }; // Write handle parameter into structure variables = new VariableInfo[variableNames.Length]; for (int i = 0; i < variables.Length; i++) { variables[i].indexGroup = (int)AdsReservedIndexGroups.SymbolValueByHandle; variables[i].indexOffset = tcClient.CreateVariableHandle(variableNames[i]); variables[i].length = variableLengths[i]; } } private AdsStream BlockRead(VariableInfo[] variables) { //Allocate memory int wrLength = variables.Length * 12; //3* INT length for every variable (group-offset-length) int rdLength = variables.Length * 4; //int-length for every variable (error) // Write data for handles into the ADS Stream BinaryWriter writer = new BinaryWriter(new AdsStream(wrLength)); for (int i = 0; i < variables.Length; i++) { writer.Write(variables[i].indexGroup); writer.Write(variables[i].indexOffset); writer.Write(variables[i].length); rdLength += variables[i].length; } // Sum command to read variables from the PLC AdsStream rdStream = new AdsStream(rdLength); tcClient.ReadWrite(0xF080, variables.Length, rdStream, (AdsStream)writer.BaseStream); // Return the ADS error codes return rdStream; }
38
private AdsStream BlockWrite(VariableInfo[] variables) { //Allocate memory int wrLength = variables.Length * 12; //3* INT length for every variable (group-offset-length) // +Valuesize for every variable for (int i = 0; i < variables.Length; i++) { wrLength += variables[i].length; } int rdLength = variables.Length * 4; //int-length for every variable (error) // Write data for handles into the ADS Stream BinaryWriter writer = new BinaryWriter(new AdsStream(wrLength)); for (int i = 0; i < variables.Length; i++) { writer.Write(variables[i].indexGroup); writer.Write(variables[i].indexOffset); writer.Write(variables[i].length); } writer.Write(Int16.Parse(textBox1.Text)); writer.Write(Int16.Parse(textBox2.Text)); writer.Write(Int16.Parse(textBox3.Text)); writer.Write(Single.Parse(textBox4.Text)); writer.Write(Int16.Parse(textBox5.Text)); // Sum command to read variables from the PLC AdsStream rdStream = new AdsStream(rdLength); tcClient.ReadWrite(0xF081, variables.Length, rdStream, (AdsStream)writer.BaseStream); // Return the ADS error codes return rdStream; } private void btnRead_Click(object sender, EventArgs e) { if (tcClient == null) return; //Get the ADS return codes and examine the errors BinaryReader binReader = new BinaryReader(BlockRead(variables)); for (int i = 0; i < variables.Length; i++) { int error = binReader.ReadInt32(); if (error != (int)AdsErrorCode.NoError) System.Diagnostics.Debug.WriteLine( String.Format("Unable to read variable {0} (Error = {1})", i, error)); } textBox1.Text = binReader.ReadInt16().ToString(); textBox2.Text = binReader.ReadInt16().ToString(); textBox3.Text = binReader.ReadInt16().ToString(); textBox4.Text = binReader.ReadSingle().ToString(); textBox5.Text = binReader.ReadInt16().ToString(); }
39
private void btnWrite_Click(object sender, EventArgs e) { if (tcClient == null) return; // Get the ADS return codes and examine for errors BinaryReader reader = new BinaryReader(BlockWrite(variables)); for (int i = 0; i < variables.Length; i++) { int error = reader.ReadInt32(); if (error != (int)AdsErrorCode.NoError) System.Diagnostics.Debug.WriteLine( String.Format("Unable to read variable {0} (Error = {1})", i, error)); } }
40
Alternative InitClient, to show you dont have to use handles for sumup
C#:
private void InitClient() { tcClient.Connect(801); //Fill structures with name and size of PLC variables variableNames = new string[] { ".VAR1", ".VAR2", ".VAR3", ".VAR4", ".VAR5" }; variableLengths = new int[] { 2, 2, 2, 4, 2 }; // Write handle parameter into structure variables = new VariableInfo[variableNames.Length]; int offset = 10; for (int i = 0; i < variables.Length; i++) { variables[i].indexGroup = (int)AdsReservedIndexGroups.PlcRWMB; variables[i].indexOffset = offset; variables[i].length = variableLengths[i]; offset = offset + 10; } }
VB.NET:
Private Sub Btn_ReadData_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Btn_ReadData.Click AantalVar = 5 SizeOfVar = 12 '(4xInt + 1xReal) dsReadData = New AdsStream(AantalVar * 4 + SizeOfVar) dsWriteData = New AdsStream(AantalVar * 12) binWriteData = New BinaryWriter(dsWriteData, System.Text.Encoding.ASCII) binReadData = New BinaryReader(dsReadData, System.Text.Encoding.ASCII) ' WriteData invullen dsWriteData.Position = 0 ' IntVar1 at %MB10 binWriteData.Write(CInt(&H4020)) binWriteData.Write(CInt(10)) binWriteData.Write(CInt(2)) ' IntVar2 at %MB20 binWriteData.Write(CInt(&H4020)) binWriteData.Write(CInt(20)) binWriteData.Write(CInt(2)) ' IntVar3 at %Mb30 binWriteData.Write(CInt(&H4020)) binWriteData.Write(CInt(30)) binWriteData.Write(CInt(2)) ' RealVar4 at %Mb40 binWriteData.Write(CInt(&H4020)) binWriteData.Write(CInt(40)) binWriteData.Write(CInt(4))
41
' IntVar5 at %MB50 binWriteData.Write(CInt(&H4020)) binWriteData.Write(CInt(50)) binWriteData.Write(CInt(2)) 'IndexGroup 'IndexOffset 'Length
' Read Data tcClient.ReadWrite(&HF080, AantalVar, dsReadData, 0, AantalVar * 4 + SizeOfVar, dsWriteData, 0, AantalVar * 12) ' Display Result dsReadData.Position ' IntVar1 at %MB10 TextBoxIntVar1.Text ' IntVar2 at %MB20 TextBoxIntVar2.Text ' IntVar3 at %Mb30 TextBoxIntVar3.Text ' RealVar4 at %Mb40 TextBoxIntVar4.Text ' IntVar5 at % mb50 TextBoxIntVar5.Text End Sub End Class
42
3.9.
SymbolInfo:
A single variable
public ITcAdsSymbol ReadSymbolInfo(string name);
Call this method to obtain information about the individual symbols (variables) in ADS devices. The returned ITcAdsSymbolinfo contains: Comment DataType IndexGroup IndexOffset Name Size Type Gets the comment behind the variable declaration. Data type of the symbol. Gets the index group of the symbol. Gets the index offset of the symbol. Gets the name of the symbol. Gets the size of the symbol. Gets the name of the symbol data type.
For our previously defined Axisstate1 structure (see 3.7 Update methods by notification): C#:
using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Text; System.Windows.Forms;
using TwinCAT.Ads; namespace Training_ADS_CNET_SYM { public partial class Form1 : Form { TcAdsClient TcClient = new TcAdsClient(); public Form1() { InitializeComponent(); TcClient.Connect(801); ReadSymbolInfo(); } private void ReadSymbolInfo() { ITcAdsSymbol symbol = TcClient.ReadSymbolInfo(".Axisstate1"); } } }
43 VB.NET:
Imports TwinCAT.Ads Public Class Form1 Dim TcClient As TcAdsClient Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load TcClient = New TcAdsClient() TcClient.Connect(801) ReadSymbolInfo() End Sub Private Sub ReadSymbolInfo() Dim symbol As ITcAdsSymbol = TcClient.ReadSymbolInfo(".Axisstate1") End Sub End Class
44
public object ReadSymbol(string name, Type type, bool reloadSymbolInfo); public object ReadSymbol(ITcAdsSymbol symbol);
Name Name of the ADS symbol. Type Managed type of the ADS symbol. Symbol The symbol that should be read. Both methods will read out the value of a symbol and returns it as an object. Strings and all primitive datatypes(UInt32, Int32, Bool etc.) are supported. The difference between both is that Arrays and Structures cannot be read with the ITcAdsSymbol-method. If we would extend our previous example: C#:
namespace Training_ADS_CNET_SYM { public partial class Form1 : Form { public struct AxisStruct { public string AxisName; public bool Ready; public bool Error; public double ActVelocity; public double ActPosition; public double SetVelocity; public double SetPosition; public double LagDistance; } TcAdsClient TcClient = new TcAdsClient(); public Form1() { InitializeComponent(); TcClient.Connect(801); ReadSymbolInfo(); } private void ReadSymbolInfo() { ITcAdsSymbol symbol = TcClient.ReadSymbolInfo(".Axisstate1"); //This will read out our struct without an error AxisStruct Axestruct = (AxisStruct)TcClient.ReadSymbol(".Axisstate1", typeof(AxisStruct), true); //Next would result in an errormessage //AxisStruct Axestruct = (AxisStruct)TcClient.ReadSymbol(symbol); } } }
45
All variables
The class TcAdsSymbolInfoLoader is responsible for downloading the list of declared variables from an ADS Server. This loader is created through the use of our TcAdsClient.
public TcAdsSymbolInfoLoader CreateSymbolInfoLoader();
The Findsymbol method does the same as the ReadSymbolInfo, except the fact that this method returns a TcAdsSymbolInfo which exposes more info like how a managed type is built up (possible structs etc.), so one could parse through them.
As the screenshot shows, our Axisstate1-variabele is made up (through the use of NextSymbol), of name, Ready, bError,
46
public TcAdsSymbolInfoCollection GetSymbols(bool forceReload);
Loads the declared symbols from the ADS device and returns them as a collection of TcAdsSymbolInfo objects. C#:
using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Text; System.Windows.Forms;
using TwinCAT.Ads; namespace Training_ADS_CNET_SYM { public partial class Form1 : Form { TcAdsClient TcClient = new TcAdsClient(); public Form1() { InitializeComponent(); TcClient.Connect(801); TcAdsSymbolInfoLoader infoloader = TcClient.CreateSymbolInfoLoader(); TcAdsSymbolInfoCollection adscollection = infoloader.GetSymbols(true); /* unused code snipped out */ } } }
47
4. Appendix
4.1. System manager Task
In addition to four PLC tasks per run-time system (and max. four PLC run-time systems), the TwinCAT system also supports further (non-PLC) software tasks which may possess I/O variables. These tasks are managed in the TwinCAT System Manager under the option Additional Tasks (below SYSTEM - Configuration in TwinCAT v2.9). These tasks can be used if a PLC is not available. Access to variables of these tasks can be gained directly from applications like TwinCAT OPC Server, Visual Basic, Delphi, VB.NET, VC++, C#.NET, etc.. e.g. via ADS-OCX, ADS-DLL or TcADS-DLL.
48 On the right side, you have access to all parameters of this task.
Name Edits the internal name of the task Port Defines the AMS port number of the task. This value must be specified! For some tasks the value is already set (e.g. for PLC tasks). Auto-Start Causes the TwinCAT System Manager to create the start command for the task so that when restarting TwinCAT the task is automatically started with configured settings. Priority Defines the priority of the Task within TwinCAT (you should ensure that priorities are not duplicated). The priority is only relevant when Auto-Start is selected. Cycle Ticks Sets the cycle time in ticks (depends upon the pre-set TwinCAT Base Time of the task). The cycle time is only relevant when Auto-Start is selected. Warning by exceeding Causes the TwinCAT to issue a warning when the pre-set task cycle time is exceeded. Message box Outputs the warning (above) also as a message box. I/O at task begin An I/O cycle is carried out at the beginning of the task. Disable Allows occasional task-disablement, i.e. the task is ignored when generating the I/O information (e.g. during commissioning). The link information is, however, retained. Create Symbols Allows access to variables of the corresponding task via ADS (e.g. from TwinCAT Scope View). Extern Sync Is this option activated, this Task will be synchronized with a configured device with Master Sync Interrupt (e.g. a SERCOS card). Tick Auto Start and Create Symbols to get things running.
49
This will open a dialogue. Name Defines the name of the variable. Comment Defines an optional comment on the new variable. Start Address Specifies the address of the variable in the tasks process image. Multiple Multiple variables of the same type can be created and added with sequential addresses. Variable Type Lists all currently recognised data types in TwinCAT System Manager from which the type of the new variable(s) can be selected. Sort by Allows the list of variable types to be sorted accordingly.
50
To connect through VariableName you have to use the fully qualified name for it, for instance: Var 1s qualified name will be TASK 1.INPUTS.Var 1. Var 2s qualified name would be TASK 1.OUTPUS.Var 2. To connect through the use of Indexgroup and index offset we see, on the right side panel ADS Info. This provides information required to access selected variable via ADS (e.g. TwinCAT Scope View, ). The information comprises Port Number, Index Group, Index Offset and Variable Length in bytes.
51
4.2.
These files are located at the paths below in a standard Visual Studio 2005 SP1 or Visual Studio 2008 installation. You can copy the files to the device using FTP or by removing the compact flash card and inserting it into a USB reader. X86 Processor (CX1000 Series): C:\Program Files\Common Files\microsoft shared\CoreCon\1.0\Target \wce400\x86 ARM Processor (CX9000 Series): C:\Program Files\Common Files\microsoft shared\CoreCon\1.0\Target\wce400 \armv4i Note, these will only work for Visual Studio 2005 SP1 or higher.
52
53
54
4.3.
Under Build (Configuration Managerin the Build column) tick the option so that the CAB file project is build automatically on each build of your project.
55
4.4.
Marshaling
This doesnt work under CE framework!!! PLC-example Global Variables: VAR_GLOBAL varTest END_VAR
ST_CodeFunction;
The variable type ST_CodeFunction could be almost everything you can imagine, for instance: Struct-example ST_CodeFunction: TYPE ST_CodeFunction : STRUCT bEnable : ActionStep : Name : Code : TimeOut : Position_X : Position_Y : Position_Z : Volume : END_STRUCT END_TYPE This would translate into: C#:
[StructLayout(LayoutKind.Sequential, Pack = 1)] public class St_CodeFunction { [MarshalAs(UnmanagedType.I1)] public Boolean bEnable; public Int16 ActionStep; //our string in PLC is 20 long + 1 ending-Char [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] public String Name; public UInt16 Code; public UInt32 TimeOut; public Single Position_X; public Single Position_Y; public Single Position_Z; public UInt32 Volume; }
(*Handled in .NET as...*) BOOL; (*Byte sized boolean*) INT:=1; (*INT16*) STRING(20); (*STRING*) WORD:=0; (*UINT16*) TIME:=t#1ms; (*UINT32...TIME takes Dword size*) REAL:=0; (*Single*) REAL:=0; (*Single*) REAL:=0; (*Single*) DWORD:=0; (*UINT32*)
56
By Object
To read/write this variable in one Read/write ADS-call this would resolve to: C#:
using TwinCAT.Ads; using System.Runtime.InteropServices; namespace MarshalingC { public partial class Form1 : Form { TcAdsClient tcClient = new TcAdsClient(); St_CodeFunction dataToRead; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { tcClient.Connect(801); try { dataToRead = (St_CodeFunction)tcClient.ReadAny( tcClient.CreateVariableHandle(".varTest"), typeof(St_CodeFunction)); } catch (Exception ex) { MessageBox.Show(ex.Message); } } } }
For the marshaling you could use a class or a struct in .NET for our codeFunction example. But this only works for the outermost type, innermost sub-types must always be structs.
57 VB.Net:
Imports TwinCAT.Ads Imports System.Runtime.InteropServices Partial Public Class Form1 Inherits Form Private tcClient As New TcAdsClient() Private dataToRead As St_CodeFunction Private Sub Form1_Load(sender As Object, e As EventArgs) tcClient.Connect(801) Try dataToRead = CType( tcClient.ReadAny( tcClient.CreateVariableHandle(".varTest"), GetType(St_CodeFunction)), St_CodeFunction) Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub End Class
58
By AdsStream
Instead of reading/writing each item from/to the stream, you could also opt to use the strength of marshaling. C#:
using TwinCAT.Ads; using System.Runtime.InteropServices; namespace MarshalingC { public partial class Form2 : Form { [StructLayout(LayoutKind.Sequential, Pack = 1)] public class St_CodeFunction { [MarshalAs(UnmanagedType.I1)] public Boolean bEnable; public Int16 ActionStep; //our string in PLC is 20 long + 1 ending-Char [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] public String Name; public UInt16 Code; public UInt32 TimeOut; public Single Position_X; public Single Position_Y; public Single Position_Z; public UInt32 Volume; public Byte[] ToByteArray() { //Create the buffer byte[] buff = new byte[Marshal.SizeOf(typeof(St_CodeFunction))]; //Tell the garbage collector to keep hands off GCHandle handle = GCHandle.Alloc(buff, GCHandleType.Pinned); //Marshall the structure Marshal.StructureToPtr( this, handle.AddrOfPinnedObject(), false); handle.Free(); return buff; } } TcAdsClient tcClient = new TcAdsClient(); St_CodeFunction dataToRead; public Form2() { InitializeComponent(); }
59
private void Form2_Load(object sender, EventArgs e) { tcClient.Connect(801); try { //Create Buffer AdsStream adsStream = new AdsStream( new byte[Marshal.SizeOf(typeof(St_CodeFunction))]); //Read in the data int bytesRead = tcClient.Read( tcClient.CreateVariableHandle(".varTest"), adsStream); //Make sure that the Garbage Collector doesn't move our buffer GCHandle handle = GCHandle.Alloc( adsStream.GetBuffer(), GCHandleType.Pinned); //Marshal the bytes dataToRead = (St_CodeFunction)Marshal.PtrToStructure( handle.AddrOfPinnedObject(), typeof(St_CodeFunction)); //Give control of the buffer back to the GC handle.Free(); dataToRead.Name = "Response from app"; tcClient.Write( tcClient.CreateVariableHandle(".varTest"), new AdsStream(dataToRead.ToByteArray())); } catch (Exception ex) { MessageBox.Show(ex.Message); } } } }
60 VB.Net:
Imports TwinCAT.Ads Imports System.Runtime.InteropServices Partial Public Class Form2 Inherits Form <StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Class St_CodeFunction <MarshalAs(UnmanagedType.I1)> _ Public bEnable As Boolean Public ActionStep As Int16 'our string in PLC is 20 long + 1 ending-Char <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=21)> _ Public Name As String Public Code As UInt16 Public TimeOut As UInt32 Public Position_X As Single Public Position_Y As Single Public Position_Z As Single Public Volume As UInt32 Public Function ToByteArray() As Byte() 'Create the buffer Dim buff As Byte() = New Byte(Marshal.SizeOf( GetType(St_CodeFunction)) - 1) {} 'Tell the garbage collector to keep hands off Dim handle As GCHandle = GCHandle.Alloc(buff, GCHandleType.Pinned) 'Marshall the structure Marshal.StructureToPtr( Me, handle.AddrOfPinnedObject(), False) handle.Free() Return buff End Function End Class Private tcClient As New TcAdsClient() Private dataToRead As St_CodeFunction
61
Private Sub Form2_Load(sender As Object, e As EventArgs) tcClient.Connect(801) Try 'Create Buffer Dim adsStream As New AdsStream( New Byte( Marshal.SizeOf(GetType(St_CodeFunction)) - 1) {}) 'Read in the data Dim bytesRead As Integer = tcClient.Read( tcClient.CreateVariableHandle(".varTest"), adsStream) 'Make sure that the Garbage Collector doesn't move our buffer Dim handle As GCHandle = GCHandle.Alloc(adsStream.GetBuffer(), GCHandleType.Pinned) 'Marshal the bytes dataToRead = DirectCast( Marshal.PtrToStructure( handle.AddrOfPinnedObject(), GetType(St_CodeFunction)), St_CodeFunction) 'Give control of the buffer back to the GC handle.Free() dataToRead.Name = "Response from app" tcClient.Write( tcClient.CreateVariableHandle(".varTest"), New AdsStream(dataToRead.ToByteArray())) Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub End Class
For a more detailed explanation, have a look at the Extending the struct sub-chapter.
62
Details
using System.Runtime.InteropServices;
This namespace provides a wide variety of members that support interoperating with unmanaged code. One important attribute you could use of this namespace, would be the MarshalAs Attribute, which you use to specify how data is marshaled between managed and unmanaged memory.
[StructLayout(LayoutKind.Sequential, Pack=1)]
This attribute lets you control the physical layout of the data fields of a class or structure. LayoutKind: lets you control how the members of the object are laid out. Sequential: The members are in the order in which they appear. The members are laid out according to the packing specified in The StructLayoutAttribute.Pack, and can be noncontiguous. Pack: controls the alignment of data fields of a class or structure in memory.
A good explanation about marshaling and packsizes can be found at: http://www.developerfusion.com/article/84519/mastering-structs-in-c/ Example: TC2: X86 -> ARM -> TC3:
63
[MarshalAs(UnmanagedType.I1)]
A Boolean in the IEC61131 would actually take up a full byte size: I1: A 1-byte signed integer. You can use this member to transform a Boolean value into a 1-byte, C-style bool (true = 1, false = 0).
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
Used for in-line, fixed-length character arrays that appears within a structure. The character type used with ByValTStr is determined by the System.Runtime.InteropServices.CharSet argument of the System.Runtime.InteropServices.StructLayoutAttribute applied to the containing structure. Always use the MarshalAsAttribute.SizeConst field to indicate the size of the array. As a string in the PLC would take up his size + 1 byte (null terminated string), in our example 20 bytes string would take up 21 characters.
64
ST_Receipt;
The variable type ST_CodeFunction could be almost everything you can imagine, for instance: Struct-example ST_Receipt: TYPE ST_Receipt : STRUCT arrayData : strArray : END_STRUCT END_TYPE C#:
[StructLayout(LayoutKind.Sequential, Pack = 1)] public Class St_Receipt { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public St_CodeFunction[] ArrayData; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public String80[] StrArray; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct String80 { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)] public string tekst; }
VB.Net:
<StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Class St_Receipt <MarshalAs(UnmanagedType.ByValArray, SizeConst:=20)> _ Public ArrayData As St_CodeFunction() <MarshalAs(UnmanagedType.ByValArray, SizeConst:=20)> _ Public StrArray As String80() End Class <StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Structure String80 <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=81)> _ Public tekst As String End Structure
As you cant marshal AND the size of the string-variable AND the size of the array, you have to split things up a bit, as shown in the above example.
65 C#:
using TwinCAT.Ads; using System.Runtime.InteropServices; namespace MarshalingC { public partial class Form1 : Form { TcAdsClient tcClient = new TcAdsClient(); St_Receipt dataToRead; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { tcClient.Connect(801); dataToRead = (St_Receipt)tcClient.ReadAny( tcClient.CreateVariableHandle(".varTest2"), typeof(St_Receipt)); } } }
VB.Net:
Imports TwinCAT.Ads Imports System.Runtime.InteropServices Partial Public Class Form1 Inherits Form Private tcClient As New TcAdsClient() Private dataToRead As St_CodeFunction Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load tcClient.Connect(801) dataToRead = DirectCast( tcClient.ReadAny( tcClient.CreateVariableHandle(".varTest"), GetType(St_CodeFunction)), St_CodeFunction) End Sub End Class