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.
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.


TwinCAT Quick Start example: Machine with Microsoft Visual C#

TwinCAT Quick Start

Example: machine with Microsoft Visual C#

在 Microsoft Visual Studio 2005 开发环境下可以建立C#工程。该实例的目的是让用户熟悉编程语言C#下集成TwinCAT ADS .NET组件。

需要的软件：

- Microsoft .NET Framework Version 2.0, 更多信息请参考http://msdn2.microsoft.com
- Microsoft Visual Studio 2005
- TwinCAT 2.10

在你建立一个C#工程前,需要确保 TwinCAT 和 PLC 程序处在活动状态。如果电脑上没装Microsoft Visual Studio 2005，请安装Microsoft .NET Framework Version 2.0。它可以在你的系统上安装所需的DLL文件。

快速入门...

我们将带您一步一步熟悉C#开发，同时配合一个例子讲解如何将我们的TwinCAT ADS .NET集成在程序中。

1. 建立一个新项目

启动 Visual Studio 2005.并建立一个新的工程，点击菜单中的 File -> New -> Project 。一个新的工程通过'New Project'对话框可以有多种模版可以选择，在'Project Types' 下我们选择 Visual C#，在 templates 对话框下我们选择 'Windows Application'。为您的工程输入一个输入一个新名字，在这里我们输入'Machine'。然后，在 'Location'中为你的项目选择路径。

2. 创建用户界面

首先 在'frmMachine'使用Toolbox在design模式下创建一个用户界面。在 Visual Studio的 Properties 的窗口下可以看到我们建立的不同控件（（比如，10个‘label’，2个‘radio button’，1个‘progress bar’，1个‘picture box’，1个‘ group box’等）。

// Group fields (group box)
grpDevice.Text = "";
grpCount.Text = "";
grpSpeed.Text = "Speed";

// Labels
lblMachine.Text = "Machine";
lblDeviceDown.Text = "Device Down";
lblDeviceUP.Text = "Device Up";
lblCount.Text = "0";
lblCountLabel.Text = "Count:";
lblSteps.Text = "Steps:";
lbl100Procent.Text = "100%";
lbl0Procent.Text = "0%";

// These are the DeviceDown and DeviceUp arrows with a different font and size (label)
DeviceDown.Text = "ê";
DeviceDown.Font = new System.Drawing.Font("Wingdings", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(2)));
DeviceUp.Text = "é";
DeviceUp.Font = new System.Drawing.Font("Wingdings", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(2)));

// Progress bar
prgSteps.Name = "prgSteps";

// Option fields (radio button)
optSpeedSlow.Text = "slow";
optSpeedFast.Text = "fast";

在屏幕左上的位置您可以看到2个输出，这个两个是对总线段子的输出。 在左边显示了工件的计数变量值。电机的转速可以通过右边区域'Speed'来改变。'Steps'显示了output1输出的进度。

3. 添加关联项

首先将 TwinCAT.Ads.dll 加入到关联列表当中。 将 TwinCAT ADS .NET 组件通过 menu -> Project -> Add Reference添加关联项。检查关联项是否 完全加入，您可以使用 Solution Explorer 查看(CTRL + W + S).。

更多信息有关于Microsoft Visual Studio .NET 集成TwinCAT ADS .NET组件请参看 here.

4. Edit source text

当TwinCAT ADS组件集成完成，界面建立，您该进行的下一步工作是改变C#源文件（source）。将 'System.IO' and 'TwinCAT.Ads' 添加到源文件（source）的顶部。
using System.IO;
using TwinCAT.Ads;

跟着在 frmMachine class 进行变量的定义。
private TcAdsClient tcClient;
private AdsStream dataStream;
private BinaryReader binReader;
private int hEngine;
private int hDeviceUp;
private int hDeviceDown;
private int hSteps;
private int hCount;
private int hSwitchNotify;
private int hSwitchWrite;

当程序启动时，函数'frmMachine_Load'调用。 这个函数连接着资源窗口形成中的'Load' 过程。 他经常用于产生不同的类和与运行中的TwinPLC建立一个连接通过TwinCAT.Ads的端口801。
//-----------------------------------------------------
//This is called first when the program is started
//-----------------------------------------------------
private void frmMachine_Load(object sender, EventArgs e)
{
    try
    {
    // Create a new instance of the AdsStream class
    dataStream = new AdsStream(7);

    // Create a new instance of the BinaryReader class
    binReader = new BinaryReader(dataStream);

    // Create a new instance of the TcAdsClient class
    tcClient = new TcAdsClient();

    // Linking with local PLC - runtime 1 - port 801
    tcClient.Connect(801);
    }
    catch
    {
    MessageBox.Show("Fehler beim Laden");
    }

    //...

连接PLC变量:

在frmMachine_Load 事件建立的同时，通过TcAdsClient.AddDeviceNotification() 方法建立了一个与PLC各个变量的连接。连接的句柄存放在一个阵列当中。由于传输方式的原因指定了数据的交换类型。AdsTransMode.OnChange方式用于仅当PLC变量发生改变时传输相应数据（详见AdsTransMode）。参数 cycleTime 设定了检查参数的检查周期。当变量改变时，变量开始连接这种方法为 'tcClient_OnNotification'。
    try
    {
        // Initialising of PLC variable monitoring
        hEngine = tcClient.AddDeviceNotification(".engine", dataStream, 0, 1, AdsTransMode.OnChange, 10, 0, null);
        hDeviceUp = tcClient.AddDeviceNotification(".deviceUp", dataStream, 1, 1, AdsTransMode.OnChange, 10, 0, null);
        hDeviceDown = tcClient.AddDeviceNotification(".deviceDown", dataStream, 2, 1, AdsTransMode.OnChange, 10, 0, null);
        hSteps = tcClient.AddDeviceNotification(".steps", dataStream, 3, 1, AdsTransMode.OnChange, 10, 0, null);
        hCount = tcClient.AddDeviceNotification(".count", dataStream, 4, 2, AdsTransMode.OnChange, 10, 0, null);
        hSwitchNotify = tcClient.AddDeviceNotification(".switch", dataStream, 6, 1,  AdsTransMode.OnChange, 10, 0, null);

        // Retrieving of the "switch" handle. This is required for writing the value
        hSwitchWrite = tcClient.CreateVariableHandle(".switch");

        // Creating an event for changes in the PLC variable values
        tcClient.AdsNotification += new AdsNotificationEventHandler(tcClient_OnNotification);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

定义:

方法 'AddDeviceNotification()' 用于连接变量。
 public int AddDeviceNotification(string variableName, AdsStream dataStream, int  offset, int length, AdsTransMode transMode, int cycleTime, int maxDelay, object userData);
  • variableName: PLC的变量名。
  • dataStream: 接收数据的数据流。
  • offset: 数据偏移量。
  • length: 数据长度。
  • transMode: 变量发生改变。
  • cycletime: 循环检测时间。
  • maxDelay: 最大延迟时间。
  • userData: 用户数据存储

方法 'CreateVariableHandle'用于连接变量 'hSwitchWrite'。

int TcAdsClient.CreateVariableHandle(string variableName);
  • variableName: PLC的变量名。

写入的方法:

现在介绍下这种方法如何实现 ('tcClient_OnNotification')。 当PLC变量发生改变时数据自动传输。

//------------------------------------------------
//This is called if one of the PLC variables changes
//------------------------------------------------
private void tcClient_OnNotification(object sender, AdsNotificationEventArgs e)
{
    try
    {
        // Setting the position of e.DataStream to the position of the current required value
        e.DataStream.Position = e.Offset;

        // Determining which variable has changed
        if(e.NotificationHandle == hDeviceUp)
        {
            //Adapting the colours of the diagrams to match the variables
            if (binReader.ReadBoolean() == true)
            {
                DeviceUp_LED.ForeColor = Color.Red;
            }
            else
            {
                DeviceUp_LED.ForeColor = Color.White;
            }
        }
        else if(e.NotificationHandle == hDeviceDown)
        {
            if (binReader.ReadBoolean() == true)
            {
                DeviceDown_LED.ForeColor = Color.Red;
            }
            else
            {
                DeviceDown_LED.ForeColor = Color.White;
            }
        }
        else if(e.NotificationHandle == hSteps)
        {
            // Setting the progress bar to the current step
            prgSteps.Value = binReader.ReadByte();
        }
        else if(e.NotificationHandle == hCount)
        {
            // Displaying the "count" value
            lblCount.Text = binReader.ReadUInt16().ToString();
        }
        else if(e.NotificationHandle == hSwitchNotify)
        {
            // Selecting the correct radio button
            if (binReader.ReadBoolean() == true)
            {
                optSpeedFast.Checked = true;
            }
            else
            {
                optSpeedSlow.Checked = true;
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}
我们还需要2个按钮设置机器转速‘fast’或‘low’。这需要两个虚拟开关通过改变PLC的内部变量来实现。
//------------------------------------------------------
//This is called if the 'slow' field is selected
//------------------------------------------------------
private void optSpeedFast_Click(object sender, EventArgs e)
{
    try
    {
        tcClient.WriteAny(hSwitchWrite, true);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

//------------------------------------------------------
//This is called if the 'fast' field is selected
//------------------------------------------------------
private void optSpeedSlow_Click(object sender, EventArgs e)
{
    try
    {
        tcClient.WriteAny(hSwitchWrite, false);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}
最后但同样重要我们需要确保这两个按键函数'optSpeedFast_Click'和'optSpeedSlow_Click'触发了正确的事件。 在设计程序的最后我们需要在函数 'optSpeedFast'点击 在资源窗口下的Events 选择'optSpeedFast_Click'。 同理我们对 'optSpeedSlow'进行同样的操作， 点击 在资源窗口下的Events 选择 'optSpeedSlow_Click'。

删除通知和句柄:

建立 frmMachine_FormClosing 并再次将事件激活， 使用函数 DeleteDeviceNotification()删除相关信息。
//------------------------------------------
//is called when the program is terminated
//------------------------------------------
private void frmMachine_FormClosing(object sender, FormClosingEventArgs e)
{
    try
    {
        // Delete notifications and handles
        tcClient.DeleteDeviceNotification(hEngine);
        tcClient.DeleteDeviceNotification(hDeviceUp);
        tcClient.DeleteDeviceNotification(hDeviceDown);
        tcClient.DeleteDeviceNotification(hSteps);
        tcClient.DeleteDeviceNotification(hCount);
        tcClient.DeleteDeviceNotification(hSwitchNotify);

        tcClient.DeleteVariableHandle(hSwitchWrite);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    tcClient.Dispose();
}

PLC程序 Machine_Final.pro可以运行在第一个PLC系统中, C# 程序 Machine.exe 可以进行测试。

下载 C# 程序事例:

CSharp example


TwinCAT Quick Start example: Machine with Microsoft Visual Basic NET

示例：用Microsoft Visual Basic .NET编写machine程序

Microsoft Visual Studio 2005开发环境下可以创建Visual Basic项目。该实例的目的是让用户熟悉TwinCAT ADS .NET组件和编程语言Visual Basic的集成。

需要的软件：

- Microsoft .NET Framework Version 2.0，更多信息请参考http://msdn2.microsoft.com

- Microsoft Visual Studio 2005
- TwinCAT 2.10

在启动Visual Basic程序前，必须先激活TwinCAT和PLC程序。如果电脑上没装Microsoft Visual Studio 2005，请安装Microsoft .NET Framework Version 2.0。它可以在你的系统上安装所需的DLL文件。

快速入门...

接下来讲一下Visual Basic程序的创建和TwinCAT ADS .NET组件的集成。

1. 创建新项目：

启动Visual Studio 2005 并创建一个新项目：从菜单栏选File -> New -> Project 。 通过'New Project'对话框可以创建基于不同模版的新项目。在'Project Types'下选择编程语言为'Visual Basic'，在模板选项中选择'Windows Application'。为你的项目命名，在这里我们输入'Machine'。然后，在 'Location'中为你的项目选择路径。

2. 创建用户界面

首先，在窗体'frmMachine'的设计模式下通过工具箱创建用户界面。在 Visual Studio 的‘Properties’窗口可以查看和设置不同的控件（比如，10个‘label’，2个‘radio button’，1个‘progress bar’，1个‘picture box’，1个‘ group box’等）。

在上图的左上角你可以看到2个输出，这两个也是对总线端子的输出。左下是为工件计数的变量。电机的转速可以通过右边的'Speed'改变。'Steps'表示在output1输出的进度。

3. 添加引用

首先必须添加一名为TwinCAT.Ads.dll的引用。TwinCAT ADS .NET的组件通过‘menu -> Project -> Add Reference‘进行添加。

更多关于TwinCAT ADS .NET的组件集成的信息查看TwinCAT ADS .NET component in Microsoft Visual Studio .NET。

4. 编辑源文本

当创建完界面并添加完TwinCAT ADS组件后，就可以修改Visual Basic源文本了。必要的命名空间'System.IO' 和 'TwinCAT.Ads'放在源文本的首行。
Imports System.IO
Imports TwinCAT.Ads

接下来是 frmMachine类中的声明。
Private tcClient As TwinCAT.Ads.TcAdsClient
Private dataStream As  TwinCAT.Ads.AdsStream
Private binReader As System.IO.BinaryReader
Private hEngine As Integer
Private hDeviceUp As Integer
Private hDeviceDown As Integer
Private hSteps As Integer
Private hCount As Integer
Private hSwitchNotify As Integer
Private hSwitchWrite As Integer
当'Windows Application'被调用时，事件'frmMachine_Load'开始执行。通过连接窗体的'Load'事件，该过程和‘Properties’窗口中的方法关联起来。在'frmMachine_Load'过程中，我们创建不同类的句柄，并且和TwinCAT.Ads组件的runtime 1 - port 801建立连接。

    '-----------------------------------------------------
    'Wird als erstes beim Starten des Programms aufgerufen
    'Is activated first when the program is started
    '-----------------------------------------------------
    Private Sub frmMachine_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            ' Create a new instance of the AdsStream class
            dataStream = New AdsStream(7)

            ' Create a new instance of the BinaryReader class
            binReader = New BinaryReader(dataStream)

            ' Create a new instance of the TcAdsClient class
            tcClient = New TwinCAT.Ads.TcAdsClient()

            ' Linking with local PLC - runtime 1 - port 801
            tcClient.Connect(801)

        Catch ex As Exception
            MessageBox.Show("Fehler beim Laden")
        End Try

'...

连接PLC变量：

在窗体的'frmMachine_Load'事件中，我们通过方法 TcAdsClient.AddDeviceNotification() 和PLC中的每一个变量建立连接。连接所用的句柄保存在一个数组中。参数'TransMode'确定数据交换的类型。在本例中，我们使用'AdsTransMode.OnChange'这种类型。这样，只有当PLC变量的值发生改变时，才进行数据传输（见AdsTransMode）。参数'cycleTime'用来确定每隔多长时间检查一次PLC变量的值是否发生变化。然后，将这些变量和方法'tcClient_OnNotification'（见 写方法'tcClient_OnNotifiaction'）连接起来。那么，当一个变量的值发生变化时，方法'tcClient_OnNotification'就被调用。

        Try
            ' Initialisieren der Überwachung der SPS-Variablen
            ' Initializing the monitoring of the PLC variables
            hEngine = tcClient.AddDeviceNotification(".engine", dataStream, 0, 1, AdsTransMode.OnChange, 10, 0, DBNull.Value)
            hDeviceUp = tcClient.AddDeviceNotification(".deviceUp", dataStream, 1, 1, AdsTransMode.OnChange, 10, 0, DBNull.Value)
            hDeviceDown = tcClient.AddDeviceNotification(".deviceDown", dataStream, 2, 1, AdsTransMode.OnChange, 10, 0, DBNull.Value)
            hSteps = tcClient.AddDeviceNotification(".steps", dataStream, 3, 1, AdsTransMode.OnChange, 10, 0, DBNull.Value)
            hCount = tcClient.AddDeviceNotification(".count", dataStream, 4, 2, AdsTransMode.OnChange, 10, 0, DBNull.Value)
            hSwitchNotify = tcClient.AddDeviceNotification(".switch", dataStream, 6, 1, AdsTransMode.OnChange, 10, 0, DBNull.Value)

            ' Holen des Handles von "switch" - wird für das Schreiben des Wertes benötigt
            ' Getting the handle for "switch" - needed for writing the value
            hSwitchWrite = tcClient.CreateVariableHandle(".switch")

            ' Erstellen eines Events für Änderungen an den SPS-Variablen-Werten
            ' Creating an event for changes of the PLC variable values
            AddHandler tcClient.AdsNotification, AddressOf tcClient_OnNotification
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub
方法 'AddDeviceNotification()' 用来连接变量。
Public Function AddDeviceNotification(ByVal variableName  as String, ByVal dataStream as TwinCAT.Ads.AdsStream,
ByVal offset As Integer, ByVal length As Integer, ByVal transMode As TwinCAT.Ads.AdsTransMode,
ByVal cycleTime As Integer, ByVal maxDelay As Integer, ByVal userData As Object)
As Integer
  • variableName: Name of the PLC variable.
  • dataStream: Data stream receiving the data.
  • offset: Interval in the data stream.
  • length: Length in the data stream.
  • transMode: Event if the variable changes.
  • cycletime: Time (in ms) after which the PLC server checks whether the variable has changed.
  • maxDelay: Latest time (in ms) after which the event has finished.
  • userData: Object that can be used for storing certain data.

方法 'CreateVariableHandle' 用来连接变量'hSwitchWrite'.

Public Function CreateVariableHandle(ByVal variableName As String) As Integer
  • variableName: Name of the PLC variable.

写方法 'tcClient_OnNotification':

当PLC的变量值发生变化时，该方法被调用。

Private Sub tcClient_OnNotification(ByVal sender As Object, ByVal e As AdsNotificationEventArgs)
        Try
            ' Setzen der Position von e.DataStream auf die des aktuellen benötigten Wertes
            ' Setting the position of e.DataStream to the position of the current needed value
            e.DataStream.Position = e.Offset

            ' Ermittlung welche Variable sich geändert hat
            ' Detecting which variable has changed
            If e.NotificationHandle = hDeviceUp Then
                'Die Farben der Grafiken entsprechened der Variablen anpassen
                'Adapt colors of graphics according to the variables
                If binReader.ReadBoolean() = True Then
                    DeviceUp_LED.ForeColor = Color.Red
                Else
                    DeviceUp_LED.ForeColor = Color.White
                End If
            ElseIf e.NotificationHandle = hDeviceDown Then
                If binReader.ReadBoolean() = True Then
                    DeviceDown_LED.ForeColor = Color.Red
                Else
                    DeviceDown_LED.ForeColor = Color.White
                End If
            ElseIf e.NotificationHandle = hSteps Then
                ' Einstellen der ProgressBar auf den aktuellen Schritt
                ' Setting the ProgressBar to the current step
                prgSteps.Value = binReader.ReadByte()
            ElseIf e.NotificationHandle = hCount Then
                ' Anzeigen des "count"-Werts
                ' Displaying the "count" value
                lblCount.Text = binReader.ReadUInt16().ToString()
            ElseIf e.NotificationHandle = hSwitchNotify Then
                ' Markieren des korrekten RadioButtons
                ' Checking the correct RadioButton
                If binReader.ReadBoolean() = True Then
                    optSpeedFast.Checked = True
                Else
                    optSpeedSlow.Checked = True
                End If

            End If
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

最后，我们还需要两个方法来实现电机转速的快、慢切换。这可以通过向PLC变量'switch'写入值来实现。

' Schreiben des Wertes von "switch"
    ' Writing the value of "switch"

    '------------------------------------------------------
    'wird aufgerufen, wenn das Feld 'fast' markiert wird
    'is activated when the 'fast' field is marked
    '------------------------------------------------------
    Private Sub optSpeedFast_Click(ByVal sender As Object, ByVal e As EventArgs) Handles optSpeedFast.Click
        Try
            ' Schreiben des Wertes von "switch"
            ' Writing the value of "switch"
            tcClient.WriteAny(hSwitchWrite, True)
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

    '------------------------------------------------------
    'wird aufgerufen, wenn das Feld 'slow' markiert wird
    'is activated when the 'slow' field is marked
    '------------------------------------------------------
    Private Sub optSpeedSlow_Click(ByVal sender As Object, ByVal e As EventArgs) Handles optSpeedSlow.Click
        Try
            tcClient.WriteAny(hSwitchWrite, False)

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

为了确保'optSpeedFast_Click' 和 'optSpeedSlow_Click'被正确的调用，在设计窗口中选择radio button'optSpeedFast'，在Properties窗口的Events栏下点'Click'，然后在后面的栏中输入'optSpeedFast_Click'。然后，会跳转到'frmMachine'的代码窗口，且光标停留在'optSpeedFast_Click'过程中，在这里可以编写具体的代码，为'hSwitchWrite'变量写入'True'。类似的可以完成radio button 'optSpeedSlow'的设置。

删除通知和句柄：

在窗体的frmMachine_FormClosing事件中，通过使用方法DeleteDeviceNotification()，这些连接被再度激活。

 '------------------------------------------
    'wird beim Beenden des Programms aufgerufen
    'is activated when ending the program
    '------------------------------------------
    Private Sub frmMachine_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        Try
            ' Löschen der Notifications und Handles
            ' Deleting of the notifications and handles
            tcClient.DeleteDeviceNotification(hEngine)
            tcClient.DeleteDeviceNotification(hDeviceUp)
            tcClient.DeleteDeviceNotification(hDeviceDown)
            tcClient.DeleteDeviceNotification(hSteps)
            tcClient.DeleteDeviceNotification(hCount)
            tcClient.DeleteDeviceNotification(hSwitchNotify)

            tcClient.DeleteVariableHandle(hSwitchWrite)
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
        tcClient.Dispose()
    End Sub

现在，你可以在runtime system 1中运行PLC程序Machine_Final.pro，然后运行你创建的VisualBasic程序Machine.exe。

下载 Visual Basic 实例:

Visual Basic Example


Sample Maschine with Microsoft Visual Basic 6.0

Sample Machine With Microsoft Visual Basic 6.0

TwinCAT ADS OCX

TwinCAT 具有几个编程接口以便与系统中的专用程序集成。来自微软的 Visual Basic 是被TwinCAT支持的一种编程语言。这种编程语言的长处是，具有图形用户接口并可将它们连接到数据库中。Visual Basic 已经被广泛地应用了几年而且现在也得到微软的大力推广。许多第三方供货商都提供用于各个领域的附加模块。这种模块被叫做OCX。Beckhoff 提供OCX 用于TwinCAT，并称其为 AdsOCX。通过TwinCAT 信息管道，AdsOCX 提供方法和其它的ADS 设备(PLC, NC/CNC, ...)通讯。 所包含的示例程序显示了如何在 Visual Basic 中存取TwinCAT PLC 服务器中的变量。

存取方法:

AdsOCX 包括各种从 其他ADS 设备读出数据的方法。采用何种方法取决于程序运行的环境。你可找到更多的AdsOCX 指令的各个特定功能的注解和详细的参考。下面仅给出各个存取方法的简要说明：

Access | Meaning
connect（连接） | 只要PLC变量和Visual Basic 变量之间需要通讯，在这两个变量之间可通过激活的方法建立连接。在进一步的程序进程中，TwinCAT 将匹配Visual Basic 变量到PLC 变量中。当Visual Basic 程序中的 PLC 变量发生变化时，这种数据交换类型也可以用于激活事件功能(事件控制的数据传送) 。
synchronous（同步的） | 在读/写方法激活后，Visual Basic程序的执行被中断，直到请求的数据到达。程序可以用新数据继续工作。
asynchronous（异步的） | 当使用异步方式时，Visual Basic程序的执行不被中断，相反，下一个命令的执行被自动继续进行。一旦请求的数据到达 AdsOCX，在Visual Basic 程序中将触发事件功能，其值将作为参数传送。

示例程序

启动 TwinCAT 和 PLC 程序：

在你启动 Visual Basic 程序之前，必须激活 TwinCAT 和 PLC 程序。

启动 Visual Basic 程序：

启动 'maschine.exe' 程序。(TwinCAT\ Samples\First Steps\)

在区域左侧，你可看到两个输出，它们也输出到总线端子。工件的计数变量显示在左侧底部。你可以在 'speed' 框中修改电机的循环速度。'position'显示对应输出到输出 1 的循环数。

程序文本：

Option Explicit

Dim deviceUp As Boolean
Dim deviceDown As Boolean
Dim steps As Integer
Dim counter As Long
Dim hDeviceUp As Long
Dim hDeviceDown As Long
Dim hSteps As Long
Dim hSwitch As Long
Dim hCounter As Long
'-----------------------------------------------------
'当程序启动时，首先被激活
'-----------------------------------------------------
Private Sub Form_Load()

    '从源文件加载语言从属字
    lblMachine.Caption = LoadResString(0 + GetLanguageId)
    lplDeviceUp.Caption = LoadResString(1 +GetLanguageId)
    lplDeviceDown.Caption = LoadResString(2 + GetLanguageId)
    lblCountLabel.Caption = LoadResString(3 + GetLanguageId)
    lplSteps.Caption =LoadResString(4 + GetLanguageId)
    fraSpeed.Caption = LoadResString(5 +GetLanguageId)
    optSpeedFast.Caption = LoadResString(6 + GetLanguageId)
    optSpeedSlow.Caption = LoadResString(7 + GetLanguageId)

    '把PLC变量和VB变量连接起来
    Call AdsOcx1.AdsReadIntegerVarConnect(".steps", 2&, 4, 55, steps)
    Call AdsOcx1.AdsReadBoolVarConnect(".deviceUp", 2&, 4, 55, deviceUp)
    Call AdsOcx1.AdsReadBoolVarConnect(".deviceDown", 2&, 4, 55, deviceDown)
    Call AdsOcx1.AdsReadLongVarConnect(".count", 4&, 4, 55, counter)

    '确定变量句柄
    Call AdsOcx1.AdsCreateVarHandle(".steps", hSteps)
    Call AdsOcx1.AdsCreateVarHandle(".deviceUp", hDeviceUp)
    Call AdsOcx1.AdsCreateVarHandle(".deviceDown", hDeviceDown)
    Call AdsOcx1.AdsCreateVarHandle(".count", hCounter)
    Call AdsOcx1.AdsCreateVarHandle(".switch", hSwitch)
End Sub

'------------------------------------------
'当结束程序时被激活
'------------------------------------------
Private Sub Form_Unload(Cancel As Integer)
    '把 Visual Basic 变量和 PLC 变量分离
    Call AdsOcx1.AdsReadIntegerDisconnect(steps)
    Call AdsOcx1.AdsReadBoolDisconnect(deviceUp)
    Call AdsOcx1.AdsReadBoolDisconnect(deviceDown)
    Call AdsOcx1.AdsReadLongDisconnect(counter)

    '释放变量句柄
    Call AdsOcx1.AdsDeleteVarHandle(hSteps)
    Call AdsOcx1.AdsDeleteVarHandle(hDeviceUp)
    Call AdsOcx1.AdsDeleteVarHandle(hDeviceDown)
    Call AdsOcx1.AdsDeleteVarHandle(hCounter)
    Call AdsOcx1.AdsDeleteVarHandle(hSwitch)
End Sub

'------------------------------------------------------
'当 'fast'域被标记时激活
'------------------------------------------------------
Private Sub optSpeedFast_Click()
    Dim switch As Boolean
    '设置 PLC 变量'switch'为 TRUE
    switch = True
    Call AdsOcx1.AdsSyncWriteBoolVarReq(hSwitch, 2&, switch)
End Sub

'------------------------------------------------------
'当 'slow' 域被标记时激活
'------------------------------------------------------
Private Sub optSpeedSlow_Click()
    Dim switch As Boolean
    '设置 PLC 变量'switch'为 FALSE
    switch = False
    Call AdsOcx1.AdsSyncWriteBoolVarReq(hSwitch, 2&, switch)
End Sub

'------------------------------------------------
'当 PLC 变量改变时激活
'------------------------------------------------
Private Sub AdsOcx1_AdsReadConnectUpdate(ByVal nIndexGroup As Long, ByVal nIndexOffset As Long)
    Select Case nIndexOffset
        Case hCounter:
        '在表单中显示数量
        lblCount.Caption = counter
    Case hDeviceUp
        '按照变量匹配图形颜色
        DeviceUp_LED.ForeColor = IIf(deviceUp = True, vbRed, vbWhite)
    Case hDeviceDown
        '按照变量匹配图形颜色
        DeviceDown_LED.ForeColor = IIf(deviceDown = True, vbRed, vbWhite)
    Case hSteps
        '显示工件的位置
        prgSteps.Width = steps * 240
    End Select
End Sub

操作原理：

在 Form1_Load() 中，与语言相关的文本均以资源装入。文本将按照 Windows NT 中预选定的语言显示。方法’AdsReadVarConnect' 将合适的 VB 变量连接到 PLC 系统。方法的参数是：

Parameter | Meaning
adsVarName | PLC变量名
cbLenght | 用字节表示的数据长度
nRefreshType | 数据传输的类型
nCycleTime | 用 ms 表示的刷新周期
pData | 数据被写入的 Visual Basic 变量

只要变量 ‘count’,’deviceUp’,’deviceDown’ 或 ‘steps’ 发生变化，AdsOcx1_AdsReadConnectUpdate() 功能就被激活。其中，表单中的对象随着 PLC 的变量而动态变化。

当用户点击 ‘fast’ 或 ‘slow’ 标识框时，optFast_Click() 和 optSlow_Click() 功能被激活。在这些功能中，'switch' 变量被设置为 TRUE 或 FALSE。TwinCAT PLC 服务器读出该变量并修改闪烁动作(PLC 程序中的 'DriveType' 功能块)。

Parameter | Meaning
hVar | PLC 变量的句柄
cbLenght | 用字节表示的数据长度
pData | 包含数据的 Visual Basic 变量

当表单被关闭时，所有不再需要的变量都应从和PLC 变量中分离开来。该操作在 Form1_Unload() 功能中完成。

下载样例：

Visual Basic 6 Sample


TwinCAT Quick Start example: Machine with Microsoft Visual C++ .NET

Example: machine with Microsoft Visual C++ .NET

在 Microsoft Visual Studio 2005 开发环境下可以建立C++工程。该实例的目的是让用户熟悉TwinCAT ADS DLL .LIB组件和编程语言C++的集成。

需要的软件：

- Microsoft .NET Framework Version 2.0 更多信息请参考 http://msdn2.microsoft.com
- Microsoft Visual Studio 2005
- TwinCAT 2.10

在你建立一个C++工程前,需要确保 TwinCAT 和 PLC 启动。如果电脑上没装Microsoft Visual Studio 2005，请安装Microsoft .NET Framework Version 2.0。它可以在你的系统上安装所需的DLL文件。

快速入门...

下面，我们将带您一步一步熟悉C++开发，同时配合一个例子讲解如何将我们的TwinCAT ADS DLL LIB 集成在程序中。

1. 创建新项目：

启动Visual Studio 2005 并创建一个新项目：从菜单栏选File -> New -> Project 。通过'New Project'对话框可以创建基于不同模版的新项目。在'Project Types'下选择编程语言为'Visual C++'，'MFC'在模板选项中选择'Windows Application'。为你的项目命名，在这里我们输入'Machine'。然后，在 'Location'中为你的项目选择路径。

在向导的欢迎窗口，请参照下图进行设置：

按照默认继续下一步。

最后一步,不需要更改，点击完成。

最后，连接所需的TcAdsDll.lib。在'Project -> Machine Properties... -> Configuration Properties -> Linker -> General -> Additional Library Directories'.输入项目路径

在‘Project -> Machine Properties... -> Configuration Properties -> Linker -> Input -> Additional Dependencies'添加Lib文件名（TcAdsDll.lib ）。

确定左上方'Configuration'选项下的'All Configurations'已被选中。

2. 创建用户界面

首先，创建一个接口模式的设计'Machine.rc'的工具箱，双击‘olution Explorer'，打开'Machine.rc'。

您现在所在的界面是'Resource View',进一步双击 'IDD_MACHINE_DIALOG' 可以看见所有需要的控件（如9个静态文本，3个图片框，2个单选按钮,一个进度条、祖框等）。

结果应该与此类似。

在上图的左上角你可以看到2个输出，这两个也是对总线端子的输出。左下是为工件计数的变量。电机的转速可以通过右边的'Speed'改变。'Steps'表示在output1输出的进度。

3 编辑源文本

一旦该接口已被创建，组件的TwinCAT DLL.LIB已经集成，您可以更改到C + +源文本。注释会创建为 'MachineDlg.h'文件。

[…]

// Variablen des Programms
// Variables of the programm
private:
    // Variablen der Bitmaps
    // Variables of the bitmaps
    CBitmap CbmUp0, CbmUp1, CbmDown0, CbmDown1;
private:
    // Timerfunktion und Variablen
    // Timerfunction and variables
    void OnTimer(UINT nIDEvent);
    UINT_PTR m_nTimer;
    UINT nIDEvent;
private:
    // Membervariable der Statusanzeige
    // Member variable of the progressbar
    CProgressCtrl	m_ctrlBar;
private:
    // Membervariablen der Controls
    // Member variables of the controls
    CStatic		m_ctrlCount;
    CStatic		m_ctrlSteps;
    CStatic		m_ctrlEngine;
    CStatic		m_Up;
    CStatic		m_Down;
    CStatic		m_rdoFast;
private:
    // Implementierung der Radio Buttons
    // Implementation of the radio buttons
    afx_msg void OnBnClickedRadio1();
    afx_msg void OnBnClickedRadio2();
private:
    // Allgemeine Variablendeklarationen
    // General declarations of variables
    CButton		*pRB;
    short		sCount;
    byte		bySteps;
    CString		sOutput;
    AmsAddr		Addr;
    PAmsAddr		pAddr;
    long		nErr, nSwitch, nNotify, nPort;
    bool		bEngine, bUp, bDown, bSwitch, bNotify;
    ULONG		hEngine, hDeviceUp, hDeviceDown, hSteps, hCount, hSwitch, hNotify;
};

使用MachineDlg::OnInitDialog()'方法时调用MFC应用程序已启动。它是用来产生不同的类的实例，建立一个与运行时系统组件TcAdsDll.lib 1通过端口801连接，加载所需的位图，并完成了PLC变量处理。

BOOL CMachineDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    […]

    // Kommunikationsport für lokale SPS (Laufzeitsystem 1) öffnen
    // Open the communication port for the SPS (Runtime system 1)
    pAddr = &Addr;
    nPort = AdsPortOpen();
    nErr = AdsGetLocalAddress(pAddr);
    pAddr->port = AMSPORT_R0_PLC_RTS1;

    // Timer initialisieren
    // Timer initialization
    m_nTimer = CDialog::SetTimer( 1, 40, NULL );
    m_ctrlBar.SetRange( 0, 25 );

    // Laden der Bitmaps
    // Loading of the bitmaps
    CbmUp0.LoadBitmapW( IDB_UP0 );
    CbmUp1.LoadBitmapW( IDB_UP1 );
    CbmDown0.LoadBitmapW( IDB_DOWN0 );
    CbmDown1.LoadBitmapW( IDB_DOWN1 );

    // Einen Pointer auf ein CButton Objekt holen
    // Get a pointer to CButton Object
    pRB = (CButton*)GetDlgItem( IDC_rdoFast );

    // Setzt den Radio Button auf "checked"
    // Set the radio button on checked state
    pRB->SetCheck(1);

    // Abfrage vom SPS-Switch-Status
    // Inquiry of the SPS switch status
    nNotify = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hNotify, 8, ".switch" );
    nNotify = AdsSyncReadReq ( pAddr, ADSIGRP_SYM_VALBYHND, hNotify, 8, &bNotify );
    if ( bNotify )
    {
      pRB = ( CButton* )GetDlgItem( IDC_rdoFast ); pRB->SetCheck(1);
      pRB = ( CButton* )GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(0);
    }
    else
    {
      pRB = ( CButton* )GetDlgItem( IDC_rdoFast ); pRB->SetCheck(0);
      pRB = ( CButton* )GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(1);
    }
    nNotify = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hNotify );

    // Handles für die SPS-Variablen holen
    // Get handles for the SPS variables
    nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hDeviceUp, 10, ".deviceUp" );
    nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hDeviceDown, 12, ".deviceDown" );
    nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hSteps, 7, ".steps" );
    nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hCount, 7, ".count" );
    nNotify = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hNotify, 8, ".switch" );

    return TRUE;  // return TRUE  unless you set the focus to a control
}

PLC输出变量

输出变量被表示成'CMachineDlg::OnTimer()'. 'SetTimer()'的参数'nElapse' (在'CMachineDlg::OnInitDialog()'中有详细说明) 需要在定时器下次执行前定义好时间（以毫秒为单位）。

void CMachineDlg::OnTimer(UINT nIDEvent)
{
    //--> Ausgabe der SPS-Variablen <--//
    //--> Output of the SPS variables <--//
    ///////////////////////////////////////////////////////////////////////////////////////////
    // Auslesen der SPS-Variablen
    // Reading the SPS variables
    nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hDeviceUp, 10, &bUp );
    // Bitmap für den "Up"-Zustand setzen
    // Sets the Bitmap of the "Up" position
    if( bUp == 0 ){ m_Up.SetBitmap(CbmUp0); }
    else{ m_Up.SetBitmap(CbmUp1); }
    //---------------------------------------------------------------------------------------//
    nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hDeviceDown, 12, &bDown );
    // Bitmap für den "Down"-Zustand setzen
    // Sets the Bitmap of the "Down" position
    if( bDown == 0 ){ m_Down.SetBitmap(CbmDown0); }
    else{ m_Down.SetBitmap(CbmDown1); }
    //---------------------------------------------------------------------------------------//
    nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hSteps, 7, &bySteps );
    // Anzahl der Schritte in das Control schreiben
    // Writes the number of steps in the control
    sOutput.Format( _T("%d"), bySteps );
    m_ctrlSteps.SetWindowText( sOutput );
    m_ctrlBar.SetPos( bySteps );
    //---------------------------------------------------------------------------------------//
    nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hCount, 7, &sCount );
    // Zählerstand in das Control schreiben
    // Writes the count in the control
    sOutput.Format( _T("%i"), sCount );
    m_ctrlCount.SetWindowText( sOutput );
    //---------------------------------------------------------------------------------------//
    // Anpassen der "Switch"-Variable, bei Veränderung in der SPS
    // Fits the switch variable if it is changing
    nNotify = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hNotify, 8, &bNotify );
    if ( bNotify )
    { pRB = (CButton*)GetDlgItem( IDC_rdoFast ); pRB->SetCheck(1);
      pRB = (CButton*)GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(0);
    }
    else
    { pRB = (CButton*)GetDlgItem( IDC_rdoFast ); pRB->SetCheck(0);
      pRB = (CButton*)GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(1);
    }
    nNotify = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hNotify );
    ///////////////////////////////////////////////////////////////////////////////////////////

    CDialog::OnTimer(nIDEvent);
};

'AdsSyncReadWriteReq()' 函数与变量进行链接。
long AdsSyncReadWriteReq(
PAmsAddr pAddr, ULONG  nIndexGroup, ULONG nIndexOffset, ULONG nReadLength, PVOID pReadData, ULONG nWriteLength,
 PVOID pWriteData );
  • pAddr: NetId的结构和ADS服务器的通道数。
  • nIndexGroup: 变量首地址。
  • nIndexOffset: 索引偏移量。
  • nReadLength: ADS驱动回馈的数据字节长度。
  • pReadData: ADS驱动回馈的数据缓冲。
  • nWriteLength: ADS驱动写入的数据字节长度。
  • pWriteData: ADS驱动写入的数据缓冲。

The method 'AdsSyncReadWriteReq()' was also used for linking the variable 'hSwitch'. The only difference is that 'AdsSyncReadReq()' (reading of variables) is used instead of 'AdsSyncWriteReq()' (writing of variables).
long AdsSyncReadReq( PAmsAddr  pAddr, ULONG nIndexGroup, ULONG nIndexOffset, ULONG nLength, PVOID pData );
  • pAddr: NetId的结构和ADS服务器的通道数。
  • nIndexGroup: 变量首地址。
  • nIndexOffset: 索引偏移量。
  • nLength: 写入ADS服务器的数据字节长度。
  • pData: ADS服务器写入位置。

最后，我们还需要2个单选按钮设置机器转速‘fast’或‘low’。这需要两个虚拟开关通过改变PLC的内部变量来实现。

单选按钮'fast' 的编码

void CMachineDlg::OnBnClickedRadio1()
{
    // Anpassen der "Switch"-Variable, bei klick auf Radio Button "fast"
    // Fits the switch variable if the radio button "fast" was clicked
    bSwitch = true;
    nSwitch = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hSwitch, 8, ".switch" );
    nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_VALBYHND, hSwitch, 0x1, &bSwitch );
    nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hSwitch );
}

单选按钮 'slow' 的编码
void CMachineDlg::OnBnClickedRadio2()
{
    // Anpassen der "Switch"-Variable, bei klick auf Radio Button "slow"
    // Fits the switch variable if the radio button "slow" was clicked
    bSwitch = false;
    nSwitch = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hSwitch, 8, ".switch" );
    nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_VALBYHND, hSwitch, 0x1, &bSwitch );
    nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hSwitch );
}

删除句柄:

最后，在进行中删掉句柄。在结尾打开'Properties' 并在顶端小图标把 'Properties' 替换成 'Messages' 。然后选择'WM_CLOSE'，并在下拉菜单中选择'<Add> OnClose'。

对'OnClose'程序进行定义，写入以下代码。

void CMachineDlg::OnClose()
{
    // Handles der SPS-Variablen freigeben und Port schließen
    // Release the handles of the SPS variables and close the port
    nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hDeviceUp );
    nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hDeviceDown );
    nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hSteps );
    nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hCount );
    nErr = AdsPortClose();

现在，你可以在runtime system 1中运行PLC程序Machine_Final.pro，然后运行你创建的C++程序Machine.exe。

下载C++ 程序 实例:

C++ .NET example


Example: machine with Microsoft Expression Blend

Microsoft Expression Blend is a program for creating program interfaces for C# and Visual Basic. In this example an interface created with the program is linked with the Machine example. The programming language C# was used.

Required software:

- Microsoft .NET Framework Version 3.0, for further information please refer to www.microsoft.com
- Microsoft Expression Blend, for further information please refer to www.microsoft.com
- Microsoft Visual Studio 2005
- TwinCAT 2.10

First steps ...

Program development with Microsoft Expression Blend and integration of the TwinCAT ADS .NET component is described based on an example.

1. Create new project:

Start Expression Blend and create a new interface via Menu -> File -> New Project... . The 'Create New Application' dialog box opens and the type, name, location and programming language can be selected. In this case please select 'Standard Application' as the type, 'Machine' as the name and 'C#' as the programming language.

2. Creating a user interface

The interface settings are stored in file Window1.xaml.

In the upper left you see the two outputs that are also output to the Bus Terminals. The bottom left shows the variable for counting the workpieces. The cycle speed of the motor can be changed via the 'Speed' field on the right. The 'Steps' display shows the number of cycles that are output on output 1.

3. Adding a reference

Once the interface has been created, add a reference called TwinCAT.Ads.dll via the menu --> Project --> Add Reference.

4. Edit source text

Once the interface has been created and the TwinCAT ADS component has been integrated, you can change to the C# source text, which can be displayed in Visual Studio. The required namespaces 'System.IO' and 'TwinCAT.Ads' are added in the top row of the source text.

using  System.IO;
using  TwinCAT.Ads;

This is followed by the declarations.

private TcAdsClient tcClient;
private AdsStream dataStream;
private BinaryReader binReader;
private int hEngine;
private int hDeviceUp;
private int hDeviceDown;
private int hSteps;
private int hCount;
private int hSwitchNotify;
private int hSwitchWrite;

The first method is the load method. It is used to generate instances of different classes and create a link to port 801.

//-----------------------------------------------------
//This is called first when the program is started
//-----------------------------------------------------
private void Load(object sender, EventArgs e)
{
    try
    {
    // Create a new instance of the AdsStream class
    dataStream = new AdsStream(7);

    // Create a new instance of the BinaryReader class
    binReader = new BinaryReader(dataStream);

    // Create a new instance of the TcAdsClient class
    tcClient = new TcAdsClient();

    // Linking with local PLC - runtime 1 - port 801
    tcClient.Connect(801);
    }
    catch
    {
    MessageBox.Show("Fehler beim Laden");
    }

    //...

The variables in the Load method are then linked and linked with a method (see "Write method 'tcClient_OnNotification'"), which is called when a variable changes.

    try
    {
        // Initialising of PLC variable monitoring
        hEngine = tcClient.AddDeviceNotification(".engine", dataStream, 0, 1, AdsTransMode.OnChange, 10, 0, null);
        hDeviceUp = tcClient.AddDeviceNotification(".deviceUp", dataStream, 1, 1, AdsTransMode.OnChange, 10, 0, null);
        hDeviceDown = tcClient.AddDeviceNotification(".deviceDown", dataStream, 2, 1, AdsTransMode.OnChange, 10, 0, null);
        hSteps = tcClient.AddDeviceNotification(".steps", dataStream, 3, 1, AdsTransMode.OnChange, 10, 0, null);
        hCount = tcClient.AddDeviceNotification(".count", dataStream, 4, 2, AdsTransMode.OnChange, 10, 0, null);
        hSwitchNotify = tcClient.AddDeviceNotification(".switch", dataStream, 6, 1,  AdsTransMode.OnChange, 10, 0, null);

        // Retrieving of the "switch" handle. This is required for writing the value
        hSwitchWrite = tcClient.CreateVariableHandle(".switch");

        // Creating an event for changes in the PLC variable values
        tcClient.AdsNotification += new AdsNotificationEventHandler(tcClient_OnNotification);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Linking PLC variables:

The method AddDeviceNotification was used for linking the variables.
 public int AddDeviceNotification(string variableName, AdsStream dataStream, int  offset, int length, AdsTransMode transMode, int cycleTime, int maxDelay, object userData);
  • variableName: Name of the PLC variable.
  • dataStream: Data stream receiving the data.
  • offset: Interval in the data stream.
  • length: Length in the data stream.
  • transMode: Event if the variable changes.
  • cycletime: Time (in ms) after which the PLC server checks whether the variable has changed.
  • maxDelay: Latest time (in ms) after which the event has finished.
  • userData: Object that can be used for storing certain data.

The method CreateVariableHandle was used for linking the variable hSwitchWrite.

int TcAdsClient.CreateVariableHandle(string variableName);
  • variableName: Name of the PLC variable.

Write method 'tcClient_OnNotification':

The method is called if one of the PLC variables has changed.

//------------------------------------------------
//This is called if one of the PLC variables changes
//------------------------------------------------
private void tcClient_OnNotification(object sender, AdsNotificationEventArgs e)
{
    try
    {
        // Setting the position of e.DataStream to the position of the current required value
        e.DataStream.Position = e.Offset;

        // Determining which variable has changed
        if(e.NotificationHandle == hDeviceUp)
        {
            //Adapting the colours of the diagrams to match the variables
            if (binReader.ReadBoolean() == true)
            {
                DeviceUp_LED.Foreground = new SolidColorBrush(Colors.Red);
            }
            else
            {
                DeviceUp_LED.Foreground = new SolidColorBrush(Colors.White);
            }
        }
        else if(e.NotificationHandle == hDeviceDown)
        {
            if (binReader.ReadBoolean() == true)
            {
                DeviceDown_LED.Foreground = new SolidColorBrush(Colors.Red);
            }
            else
            {
                DeviceDown_LED.Foreground = new SolidColorBrush(Colors.White);
            }
        }
        else if(e.NotificationHandle == hSteps)
        {
            // Setting the progress bar to the current step
            prgSteps.Value = binReader.ReadByte();
        }
        else if(e.NotificationHandle == hCount)
        {
            // Displaying the "count" value
            lblCount.Text = binReader.ReadUInt16().ToString();
        }
        else if(e.NotificationHandle == hSwitchNotify)
        {
            // Selecting the correct radio button
            if (binReader.ReadBoolean() == true)
            {
                optSpeedFast.IsChecked = true;
            }
            else
            {
                optSpeedSlow.IsChecked = true;
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}
Lastly, we require two methods for making the machine faster or slower. They are used to switch a virtual switch by writing a value to the PLC variable 'switch'.
//------------------------------------------------------
//This is called if the 'slow' field is selected
//------------------------------------------------------
private void optSpeedFast_Click(object sender, EventArgs e)
{
    try
    {
        tcClient.WriteAny(hSwitchWrite, true);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

//------------------------------------------------------
//This is called if the 'fast' field is selected
//------------------------------------------------------
private void optSpeedSlow_Click(object sender, EventArgs e)
{
    try
    {
        tcClient.WriteAny(hSwitchWrite, false);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

To ensure that the methods are called for the right event, select DocumentRoot in the Design view, click add under Events and select loaded, and select the Load method.

The same applies for the two radio button, although in this case select the event 'Click' and the method 'optSpeedFast_Click' or 'optSpeedSlow_Click_Click'.

Delete notifications and handles:

In the Close event of the window the links are activated again with the method DeleteDeviceNotification().

private void Close(object sender, EventArgs e)
{
    try
    {
        // Delete notifications and handles
        tcClient.DeleteDeviceNotification(hEngine);
        tcClient.DeleteDeviceNotification(hDeviceUp);
        tcClient.DeleteDeviceNotification(hDeviceDown);
        tcClient.DeleteDeviceNotification(hSteps);
        tcClient.DeleteDeviceNotification(hCount);
        tcClient.DeleteDeviceNotification(hSwitchNotify);

        tcClient.DeleteVariableHandle(hSwitchWrite);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    tcClient.Dispose();
}

The PLC program Machine_Final.pro should start on runtime system 1, and the created C# program Machine.exe can be tested.

Download:

Expression Blend example


TwinCAT Quick Start example: Machine with Microsoft Expression Blend in Microsoft Windows Vista Media Center

Example: Machine with Microsoft Expression Blend in Microsoft Windows Vista Media Center

Microsoft Expression Blend is a program for creating program interfaces for C# and Visual Basic. In this example an interface created with the program is linked with the Machine example and subsequently integrated in the Microsoft Windows Vista Media Center. The programming language C# was used.

Required software:

- Microsoft .NET Framework Version 3.0, for more information click here - Microsoft Expression Blend, for further information click here
- Microsoft Visual Studio 2005
- Microsoft Windows Vista Media Center
- Microsoft Windows SDK for .Net Framework 3.0, for more information click here
- Microsoft Visual Studio 2005 extensions for .Net Framework 3.0 (November 2006 CTP), for more information click here
- TwinCAT 2.10
- Notepad or other text editor

First steps ...

Step by step familiarisation with the development of a program with Microsoft Visual Studio and Microsoft Expression Blend, integration of the TwinCAT ADS .NET component based on an example, and integration in the Microsoft Windows Vista Media Center.

1. Create new project:

Start Microsoft Visual Studio and create new XAML browser application. Proceed via the menu 'File -> New -> Project...' . The dialog box 'New Project' opens.

First select the project type: 'Project types -> Visual C# -> Net Framework 3.0'. The project type templates appear on the right. Select 'XAML Browser Application'. Enter a name for your project (in this case Machine) and specify the location.

2. Creating a user interface

Now change to Microsoft Expression Blend and open the project you just created in order to create the user interface.

In the upper left you see the two outputs that are also output to the Bus Terminals. The bottom left shows the variable for counting the workpieces. The cycle speed of the motor can be changed via the 'Speed' field on the right. The 'Steps' display shows the number of cycles that are output on output 1.

To make the user interfaces constantly adapt their size, copy the upper grid and insert a view box instead of the grid. Now insert the grid into the view box. Now set the size of the page, the view box and the grid to 'Auto'. This may result in shifting of elements, in which case you have to reposition them. Please ensure that the size of the page, the view box and the grid is not inadvertently reset to "fixed".

3. Adding a reference

Once the interface has been created, add a reference called 'TwinCAT.Ads.dll'. This can be done in Visual Studio or Expression Blend. In both cases proceed via the menu 'Project --> Add Reference'.

4. Security activation

Select 'Project -> <Project Name> Properties....' from the menu . .

A tab opens, in which you can specify the project properties. Select 'Security' and then 'this is a full trust application'.

5. Edit source text

You can now start creating the source text in C#.
The required namespaces 'System.IO' and 'TwinCAT.Ads' are added in the top row of the source text.

using  System.IO;
using  TwinCAT.Ads;

The next step involves the declarations.

private TcAdsClient tcClient;
private AdsStream dataStream;
private BinaryReader binReader;
private int hEngine;
private int hDeviceUp;
private int hDeviceDown;
private int hSteps;
private int hCount;
private int hSwitchNotify;
private int hSwitchWrite;

The first method is the 'Load' method. It is used to generate instances of different classes and create a link to port 801.

//-----------------------------------------------------
// Wird als erstes beim Starten des Programms aufgerufen
// Is activated first when the program is started
//-----------------------------------------------------
private void Load(object sender, EventArgs e)
{
    try
    {
    // Eine neue Instanz der Klasse AdsStream erzeugen
    // Create an new instance of the AdsStream class
    dataStream = new AdsStream(7);

    // Eine neue Instanz der Klasse BinaryReader erzeugen
    // Create a new instance of the BinaryReader class
    binReader = new BinaryReader(dataStream);

    // Eine neue Instanz der Klasse TcAdsClient erzeugen
    // Create an new instance of the TcAdsClient class
    tcClient = new TcAdsClient();

    // Verbinden mit lokaler SPS - Laufzeit 1 - Port 801
    // Connecting to local PLC - Runtime 1 - Port 801
    tcClient.Connect(801);
    }
    catch
    {
    MessageBox.Show("Fehler beim Laden");
    }

    //...

The variables in the 'Load' method are then linked and linked with a method (that still has to be written), which is called when a variable changes.

    try
    {
        // Initialisieren der Überwachung der SPS-Variablen
        // Initalizing the monitoring of the PLC variables
        hEngine = tcClient.AddDeviceNotification(".engine", dataStream, 0, 1, AdsTransMode.OnChange, 10, 0, null);
        hDeviceUp = tcClient.AddDeviceNotification(".deviceUp", dataStream, 1, 1, AdsTransMode.OnChange, 10, 0, null);
        hDeviceDown = tcClient.AddDeviceNotification(".deviceDown", dataStream, 2, 1, AdsTransMode.OnChange, 10, 0, null);
        hSteps = tcClient.AddDeviceNotification(".steps", dataStream, 3, 1, AdsTransMode.OnChange, 10, 0, null);
        hCount = tcClient.AddDeviceNotification(".count", dataStream, 4, 2, AdsTransMode.OnChange, 10, 0, null);
        hSwitchNotify = tcClient.AddDeviceNotification(".switch", dataStream, 6, 1,  AdsTransMode.OnChange, 10, 0, null);

        // Holen des Handles von 'switch' - wird für das Schreiben des Wertes benötigt
        // Getting the handle for 'switch' - needed for writing the value
        hSwitchWrite = tcClient.CreateVariableHandle(".switch");

        // Erstellen eines Events für Änderungen an den SPS-Variablen-Werten
        // Creating an event for changes of the PLC variable value
        tcClient.AdsNotification += new AdsNotificationEventHandler(tcClient_OnNotification);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

6. Definition

Linking PLC variables:
The method AddDeviceNotification was used for linking the variables.
 public int AddDeviceNotification(string variableName, AdsStream dataStream, int  offset, int length, AdsTransMode transMode, int cycleTime, int maxDelay, object userData);
  • variableName: Name of the PLC variable.
  • dataStream: Data stream receiving the data.
  • offset: Interval in the data stream.
  • length: Length in the data stream.
  • transMode: Event if the variable changes.
  • cycletime: Time (in ms) after which the PLC server checks whether the variable has changed.
  • maxDelay: Latest time (in ms) after which the event has finished.
  • userData: Object that can be used for storing certain data.

The method CreateVariableHandle was used for linking the variable 'hSwitchWrite'.

int TcAdsClient.CreateVariableHandle(string variableName);

  • variableName: Name of the PLC variable.

7. Write method:

A method that does not exist yet was referred to above. This method ('tcClient_OnNotification') is written next. The method is called if one of the PLC variables has changed.

//------------------------------------------------
// wird bei Änderung einer SPS-Variablen aufgerufen
// is activated when a PLC variable changes
//------------------------------------------------
private void tcClient_OnNotification(object sender, AdsNotificationEventArgs e)
{
    try
    {
        // Setzen der Position von e.DataStream auf die des aktuellen benötigten Wertes
        // Setting the position of e.DataStream to the position of the current needed value
        e.DataStream.Position = e.Offset;

        // Ermittlung welche Variable sich geändert hat
        // Detecting which variable has changed
        if(e.NotificationHandle == hDeviceUp)
        {
            // Die Farben der Grafiken entsprechened der Variablen anpassen
            // Adapt colors of graphice according to the variables
            if (binReader.ReadBoolean() == true)
            {
                DeviceUp_LED.Foreground = new SolidColorBrush(Colors.Red);
            }
            else
            {
                DeviceUp_LED.Foreground = new SolidColorBrush(Colors.White);
            }
        }
        else if(e.NotificationHandle == hDeviceDown)
        {
            if (binReader.ReadBoolean() == true)
            {
                DeviceDown_LED.Foreground = new SolidColorBrush(Colors.Red);
            }
            else
            {
                DeviceDown_LED.Foreground = new SolidColorBrush(Colors.White);
            }
        }
        else if(e.NotificationHandle == hSteps)
        {
            // Einstellen der ProgressBar auf den aktuellen Schritt
            // Setting the ProgressBar to the current step
            prgSteps.Value = binReader.ReadByte();
        }
        else if(e.NotificationHandle == hCount)
        {
            // Anzeigen des 'Zähler'-Wertes
            // Displaying the 'count' value
            lblCount.Text = binReader.ReadUInt16().ToString();
        }
        else if(e.NotificationHandle == hSwitchNotify)
        {
            // Markieren des korrekten RadioButtons
            // Checking the correct RadioButton
            if (binReader.ReadBoolean() == true)
            {
                optSpeedFast.IsChecked = true;
            }
            else
            {
                optSpeedSlow.IsChecked = true;
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}
Lastly, we require two methods for setting the machine to fast or slow. They are used to switch a virtual switch by writing a value to the PLC variable 'switch'.
//------------------------------------------------------
// wird aufgerufen, wenn das Feld 'schnell' markiert wird
// is activated when the 'fast' field is marked
//------------------------------------------------------
private void optSpeedFast_Click(object sender, EventArgs e)
{
    try
    {
        tcClient.WriteAny(hSwitchWrite, true);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

//------------------------------------------------------
// wird aufgerufen, wenn das Feld 'langsam' markiert wird
// is activated when the 'slow' field is marked
//------------------------------------------------------
private void optSpeedSlow_Click(object sender, EventArgs e)
{
    try
    {
        tcClient.WriteAny(hSwitchWrite, false);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

8. Delete notifications and handles:

In the Close event of the window the links are activated again with the method DeleteDeviceNotification().

//------------------------------------------------------
// wird beim Beenden des Programms aufgerufen
// is activated when ending the program
//------------------------------------------------------
private void Close(object sender, EventArgs e)
{
    try
    {
        // Löschen der Notifications und Handles
        // Deleting of the notification and handles
        tcClient.DeleteDeviceNotification(hEngine);
        tcClient.DeleteDeviceNotification(hDeviceUp);
        tcClient.DeleteDeviceNotification(hDeviceDown);
        tcClient.DeleteDeviceNotification(hSteps);
        tcClient.DeleteDeviceNotification(hCount);
        tcClient.DeleteDeviceNotification(hSwitchNotify);

        tcClient.DeleteVariableHandle(hSwitchWrite);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    tcClient.Dispose();
}

Last but not least we need to ensure that the methods are called for the right event. To this end open Expression Blend, select Page, change to Events in Properties and enter 'Load' under 'Loaded' and 'Close' under 'Unloaded'. Proceed accordingly for the two radio buttons, except that the 'Click' event should be selected and the method 'optSpeedFast_Click' or 'optSpeedSlow_Click'.
The Machine_Final.pro PLC machine program must run in runtime system 1, and the program can be tested in Internet Explorer 7.

9. Integration in Vista Media Center

Once you have tested your project sufficiently and no errors were detected, you can integrate it in the Microsoft Windows Vista Media Center.
In Visual Studio call up the project properties again, but this time select 'Publish'. Click on 'Publish Now'. This creates an xbap file which you subsequently call up in the Media Center. This step is always required whenever the program has been modified and the change is to be transferred to the Media Center.

Now open a text editor (e.g. Notepad) and enter:
<application
        URL = "C:\Users\<User>\Documents\Visual Studio 2005\Projects\Machine\Machine\Publish\Machine.xbap">
</application>

Save this under: 'C:\Users\<User>\AppData\Roaming\Media Center Programs\Machine.mcl'. If you now start your Media Center you will find your program under 'Online Media -> program library -> programs by name -> Machine'.
This is the simplest form of integration in the Microsoft Windows Vista Media Center. Further information on integration in the Media Center can be found here.

10. Download Expression Blend example:

Expression example


TwinCAT Quick Start - Beispiel Maschine mit Silverlight JavaScript

Sample Machine with Microsoft Silverlight and JavaScript

Microsoft Silverlight is a web-presentation-technology.

Required Software:

- runtime:
- Microsoft Silverlight 1.0 / 1.1

- developer tools:
- Microsoft Visual Studio 2008 Beta 2
- Microsoft Silverlight Tools Alpha Refresh for Visual Studio (July 2007)
or
- Microsoft Visual Studio 2005
- Microsoft Silverlight 1.0 Software Development Kit
For this sample Microsoft Visual Studio 2005 was used.

- designer tools:
- Expression Blend 2

- others:
- TwinCAT 2.10
- Browser (for example Internet Explorer 7 or Mozilla Firefox 2)
- Microsoft .NET Framework Version 3.0

The first steps...

Step for step you learn to develop a Silverlight-Application and include the TwinCAT ADS Web Service on a sample.

1. Create new Project:

Start the Microsoft Expression Blend 2 and create a new user interface over 'Menu → File → new Project...' . If the Dialog 'Create New Project' open, now you can edit the type, the name, the location and the language. In this sample choose the type 'Silverlight Application (JavaScript)' and the name 'Machine'.

2. Create user interface:

A few controls are available for the development of the user interfaces in Silverlight applications. You can create a RadioButton with a Textbox and two Ellipses, whitch are grouped into a Canvas. The Progressbar can be created with three Rectangles which are grouped into a separate Canvas again.
The properties of the interface are described in the Page.xaml file.
Note that you can not set the object size to 'Auto'. This may lead to errors later.

You can see the two outputs in the upper left corner. The variable, which counts the work pieces, is placed below. You can change the engine speed with the two RadioButtons on the right side. The displayed steps are equivalent to the number of clocks that are returned to output 1.

3. Add XMLHTTP.JS

In Visual Studio over 'Project → Add Existing Item...' add the file 'XMLHTTP.JS'. This file contains general methods for reading and writing of PLC variables and converting data types.

4. Edit source code

Open the file 'Default.html' in Visual Studio and include the 'XMLHTTP.JS' file into the header.

<script type="text/javascript" src="xmlhttp.js"></script>

Add a JavaScript-range in the HEAD-field of the HTML page. You find the following source code in it:

The global variables has to be declared first.

<script type="text/javascript">

    //enter URL to webservice here:
    var url = "http://localhost/TcAdsWebService/TcAdsWebService.dll";
    //enter netId here:
    var netId = "172.16.2.63.1.1";
    //enter the port here:
    var port = 811;
    //send soap request every x seconds:
    var refresh = 1000;

    var inuse = false;

    var b64s, success, errors, req;

    var vUp, vDown, vProgressbar, vCount, vFast, vSlow;

...

You have to adapt the url of the TcAdsWebService, the netID and the port.

The GUI objects can not be accessed directly. In order to do this the objects will be assigned to already declared variables after the start of the application.

function Load(sender, eventArgs)
{
    vUp = sender.findName("pathUp");
    vDown = sender.findName("pathDown");
    vProgressbar = sender.findName("recProgressbar");
    vCount = sender.findName("txbCount");
    vFast = sender.findName("ellPointFast");
    vSlow = sender.findName("ellPointSlow");
}

The loading method contains the assignation of the variables. For executing the Load method Loaded="Load" has to be added to the Canvas on top of the Page.xaml.

<Canvas
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="368" Height="256"
    x:Name="Page"
    Loaded="Load"
>
...

function loop(x)
{
    Read(netId, port, '16416', '0', '86'); //send soap read request via xmlhttprequest
    window.setTimeout("loop("+x+")", x);
}

Read PLC variable

Read(netId, nPort, indexGroup, indexOffset, cbLen)
  • netId: String indicating the AMS-Net-Id of the PLC
  • nPort: Port-number of the runtime-system
  • indexGroup: IndexGroup of the variables to read
  • indexOffset: First byte to read
  • ncbLen: Number of bytes to read

function init()
{
    b64s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    success = 0;
    errors = 0;
    loop(refresh); //send request every x seconds
}

Add onload="init()" into <body>, so that the method will be executed during loading.

<body onload="init()" >

The next function reads values and displays them. It is important that the address of the variable in Machine.pro is correctly. See also: Declaration of variables...

function processReqChange()
{
    /*
    readyStates:
    0 = uninitialized
    1 = loading
    2 = loaded
    3 = interactive
    4 = complete
    */

    if (req.readyState == 4)
    {
        // only if "OK"
        if (req.status == 200)
        {
            response = req.responseXML.documentElement;

            inuse = false;

            try //check if there was an error in the request
            {
                errortext = response.getElementsByTagName('faultstring')[0].firstChild.data;
                try
                {
                    errorcode = response.getElementsByTagName('errorcode')[0].firstChild.data;
                }
                catch (e){errorcode="-";}
                    alert(errortext + " ("+errorcode+")");
                return;
            }
            catch (e)
            {
                errorcode=0;
            }

            var data;
            try //if the server returns a <ppData> element decode it, otherwise (write request) do nothing
            {
                data = response.getElementsByTagName('ppData')[0].firstChild.data;
                mode = "read";
            }
            catch (e)
            {
                data = "";
                mode = "";
            }

            if (mode=="read")
            {
                try
                {
                    data = b64t2d(data); //decode result string

                    steps = toInt(data.substr(1, 2));

                    bool = toInt(data.substr(5,2));
                    bool2 = toInt(data.substr(4,2));

                    count = toInt(data.substr(3, 2));

                    speed = toInt(data.substr(6, 2));
                }
                catch (e)
                {
                    alert("Parsing Failed:" + e);
                    return;
                }

                vProgressbar.Width = 306.321/100*steps*4;

                if (bool2 != "1")
                    { vCount.Text = count.toString();}

                if (bool == "1")
                    { vUp.Opacity=1.0;
                      vDown.Opacity=0.0; }
                else if (bool2 == "1")
                    { vUp.Opacity=0.0;
                      vDown.Opacity=1.0; }
                else
                    { vUp.Opacity=0.0;
                      vDown.Opacity=0.0; }

                if (speed == "0")
                   { vFast.Opacity=1.0;
                     vSlow.Opacity=0.0; }
                else
                   { vSlow.Opacity=1.0;
                     vFast.Opacity=0.0; }

            }
        }
        else alert(req.statusText+" "+req.status); //cannot retrieve xml data
    }
}

With the last two methods the PLC variable, which controls the speed of the machine, is set to zero or one.

function Fast_MouseLeftButtonDown(sender, eventArgs)
{
    Write(netId, port, '16416', '6', '2', '0', 'int');
}
function Slow_MouseLeftButtonDown(sender, eventArgs)
{
    Write(netId, port, '16416', '6', '2', '1', 'int');
}

Write SPS Variables

Write(netId, port, indexGroup, indexOffset, cbLen, pwrData, type)
  • netId: String indicating the AMS.Net.Id of the PLC
  • nPort: Port-number of the runtime-system
  • indexGroup: IndexGroup of the variable
  • indexOffset: First byte to write to
  • ncbLen: Number of bytes to write
  • pwrData: Array containing the data to write
  • type: "bool", "int" or "string"

It has to be changed to 'Expression Blend 2' as it has been done with the 'Load' function for declaring the according rows as 'Click' event for both buttons.

<Canvas x:Name="canvasFast" MouseLeftButtonDown="Fast_MouseLeftButtonDown" ...
<Canvas x:Name="canvasSlow" MouseLeftButtonDown="Slow_MouseLeftButtonDown" ...

5. Testing:

By debugging the application you will recognize that it will not work as expected. To prevent this got to 'Build -> Publish'.

Choose the 'Target location' in the dialog window and then 'All project files' under 'Copy'.

If the statusbar says 'Publish succeded', you can run and test the application in the browser.

Download:

Silverlight-Sample


TcQuickStart Silverlight for Windows Embedded Machine Sample

Example for a machine with Microsoft Silverlight for Windows Embedded

A new item in Windows Embedded CE 6.0 R3 is Silverlight for Windows Embedded. With this new technology, user interfaces of CE devices can now be written in XAML and designed with tools such as Microsoft Expression Blend. On the basis of the machine example, the creation of a Silverlight for Windows Embedded application with integration of the ADS components is described here .

Required software

- Microsoft Visual Studio 2008
- Microsoft Expression Blend 2 SP1
- TwinCAT 2.11

Required hardware

- Windows CE 6.0 R3 device (e.g. CX1020)

First steps...

1. Creating a new Silverlight 2 project:

The design of a Silverlight for Windows Embedded application is written in XAML. To this end a Silverlight 2 project is created with Microsoft Expression Blend 2 SP1 via 'File - > New Project'. The Visual Studio Solution thereby created is not needed in this example. In addition, the selection of the programming language (Visual C# or Visual Basic) can be ignored. Silverlight for Windows Embedded supports only Visual C++, which is not integrated in Expression Blend. It is therefore also not possible to use the source code generated by this tool. It is best to deactivate the integration of Visual Studio in Expression Blend in order to avoid the unnecessary automatic generation of Visual C# and Visual Basic code. To do this, select 'Clipboard only' under 'Options->Event handlers'.

2. Creating a user interface

The user interface can now be created in Expression Blend.

In the upper left area, the two outputs can be seen that are also output to the bus terminals. The bottom left shows the variable for counting the workpieces. The speed can be set on the right. The 'Steps' display corresponds to the number of cycles. In addition, a button for ending the program is created at the top right.

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="319" Height="255">

    <!-- Timelines -->
    <UserControl.Resources>

        <!-- Timeline Device Down -->
        <Storyboard x:Name="timelineDeviceDown">
            <ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                    Storyboard.TargetName="txtDeviceDown"
                    Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FFFF0000"/>
            </ColorAnimationUsingKeyFrames>
        </Storyboard>

        <!-- Timeline Device Up -->
        <Storyboard x:Name="timelineDeviceUp">
            <ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                    Storyboard.TargetName="txtDeviceUp"
                    Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.4000000" Value="#FFFF0000"/>
            </ColorAnimationUsingKeyFrames>
        </Storyboard>

        <!-- Timeline Engine -->
        <Storyboard x:Name="timelineEngine"/>

    </UserControl.Resources>

    <!-- Beginn der Layout Beschreibung -->
    <Grid x:Name="LayoutRoot" Background="#FF595959">

        <!-- Title Machine -->
        <TextBlock Text="Machine" Margin="80,8.438,80,0" VerticalAlignment="Top"
                FontWeight="Bold" Foreground="#FFFFFFFF" FontSize="34"/>

        <!-- Device Up / Device Down -->
        <Grid Margin="15,60,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"
                Height="53" Width="132.532">
            <Rectangle Fill="{x:Null}" Stroke="#FFFFFFFF" StrokeThickness="2"
                    RadiusX="6" RadiusY="6"/>
            <TextBlock Text="Device Up" Margin="28.823,5.396,-8.823,0"
                    VerticalAlignment="Top" Foreground="#FFFFFFFF" FontSize="16"/>
            <TextBlock Text="Device Down" Margin="29.002,0,-9.002,4.994"
                    VerticalAlignment="Bottom" Foreground="#FFFFFFFF" FontSize="16"/>
            <TextBlock x:Name="txtDeviceDown" Text="ê" Margin="7.517,0,0,4.998"
                    HorizontalAlignment="Left" VerticalAlignment="Bottom" FontFamily="Wingdings"
                    FontWeight="Bold" Foreground="#FFFFFFFF" FontSize="16"/>
            <TextBlock x:Name="txtDeviceUp" Text="é" Margin="7.517,5.497,0,0"
                    HorizontalAlignment="Left" VerticalAlignment="Top" FontFamily="Wingdings"
                    FontWeight="Bold" Foreground="#FFFFFFFF" FontSize="16"/>
        </Grid>

        <!-- Counter -->
        <Grid Margin="15,0,0,71" HorizontalAlignment="Left" VerticalAlignment="Bottom"
                Height="53" Width="133">
            <Rectangle Fill="{x:Null}" Stroke="#FFFFFFFF" StrokeThickness="2"
                    RadiusX="6" RadiusY="6"/>
            <StackPanel Margin="12.991,16,0,16" HorizontalAlignment="Left"
                    Orientation="Horizontal" Width="113">
                <TextBlock Text="Count:" Width="54.824" Foreground="#FFFFFFFF"
                FontSize="16"/>
                <TextBlock x:Name="txtCount" Margin="2,0,0,0" Foreground="#FFFFFFFF"
                        FontSize="16"/>
            </StackPanel>
        </Grid>

        <!-- Speed -->
        <Grid Margin="0,0,15,71" Height="53" HorizontalAlignment="Right"
                VerticalAlignment="Bottom" Width="132.532">
            <Rectangle Fill="{x:Null}" Stroke="#FFFFFFFF" StrokeThickness="2"
                    RadiusX="6" RadiusY="6"/>
            <StackPanel Margin="12.988,0,3.012,0" Orientation="Horizontal">
                <RadioButton x:Name="radSpeedSlow" Content="slow" Margin="0,0,6,0"
                        Foreground="#FFFFFFFF" FontSize="16" Height="19.496"/>
                <RadioButton x:Name="radSpeedFast" Content="fast" Foreground="#FFFFFFFF"
                        FontSize="16" Height="19.496"/>
            </StackPanel>
        </Grid>

        <!-- Steps -->
        <Grid Margin="15,0,15,5" VerticalAlignment="Bottom"
        Height="57">
            <ProgressBar x:Name="prgSteps" Margin="0,18,0,18"
            Maximum="25"/>
            <TextBlock Text="Steps:" HorizontalAlignment="Left"
                    VerticalAlignment="Top" Foreground="#FFFFFFFF"/>
            <TextBlock Text="0%" HorizontalAlignment="Left"
                    VerticalAlignment="Bottom" Foreground="#FFFFFFFF"/>
            <TextBlock Text="100%" HorizontalAlignment="Right"
                    VerticalAlignment="Bottom" Foreground="#FFFFFFFF"/>
        </Grid>

        <!-- Close Button -->
        <Button x:Name="butClose" Content="X"
        HorizontalAlignment="Right" VerticalAlignment="Top"
                Height="20" Width="20"/>

        <!-- Beckhoff Logo -->
        <Image Source="beckhoff_logo_white.jpg" Margin="0,64.502,15,0" HorizontalAlignment="Right"
                VerticalAlignment="Top" Height="43.151" Width="133.745"
                Stretch="Fill"/>
     </Grid>
</UserControl>

3. Creating a new Win32 Smart Device project

A new Win32 Smart Device project must now be created in Visual Studio 2008.

4. Integrating the XAML file as a resource

The user interface designed with Expression Blend can be integrated in the new project. To do this, open the resource file (.rc). A right mouse click on the resource in the Resource View Tab and the selection of 'Add - > Resource…' opens a dialogue box via which the XAML file can be integrated.

The XAML file can be imported into the project with the aid of the dialogue.

Specify XAML as the resource type.

The standard resource ID (IDR_XAML1) can be retained in this example. However, in real projects it is a good idea to rename this.

5. Creating an AdsHelper Class

The largest part of the Ads communication can be stored in a separate class, called AdsHelper here.

AdsHelper.h

The TcAds headers are integrated in the AdsHelper headers,
#include "..\_AdditionalFiles\TcpAdsApiCe\include\TcAdsDef.h"
#include "..\_AdditionalFiles\TcpAdsApiCe\include\TcAdsAPI.h"

where the declarations are also made.
typedef enum E_NOTIFICATION_IDENT
{
    engine = 0,
    deviceUp = 1,
    deviceDown = 2,
    steps,
    count,
    switchSpeed
};

long AdsGetVarHandle(AmsAddr* pServerAddr, const char* szVarname, long* pHandle);
long AdsFreeVarHandle(AmsAddr* pServerAddr, long handle);
long AdsGerVarHandleEx(long port, AmsAddr* pServerAddr, const char* szVarname, long* pHandle);
long AdsFreeVarHandleEx(long port, AmsAddr* pServerAddr, long handle);

long SpeedSlowEx(long port, AmsAddr* pServerAddr);
long SpeedFastEx(long port, AmsAddr* pServerAddr);

long connect(long port, AmsAddr* pServerAddr);
long disconnect(long port, AmsAddr* pServerAddr);

AdsHelper.cpp

The headers StdAfx.h and AdsHelper.h must be integrated in the AdsHelper.cpp,
#include "StdAfx.h"
#include "AdsHelper.h"

and afterwards the global variables defined.

long hEngine, hDeviceUp, hDeviceDown, hSteps, hCount, hSwitch;
unsigned long hEngineNotification, hDeviceUpNotification, hDeviceDownNotification,
              hStepsNotification, hCountNotification, hSwitchNotification;

The methods AdsGetVarHandle and AdsGetVarHandleEx serve to create handles for PLC variables
long AdsGetVarHandle(AmsAddr* pServerAddr, const char* szVarname, long* pHandle)
{
    if (pHandle == NULL || pServerAddr == NULL)
        return E_POINTER;

    unsigned long read = 0;
    long nErr =
        AdsSyncReadWriteReqEx(pServerAddrm ADSIGRP_SYM_HNDBYNAME, 0x0,
        sizeof(long), pHandle,strlen(szVarname), (char*)szVarname, &read);

    return nErr;
}

long AdsGetVarHandleEx(long port, AmsAddr* pServerAddr, const char* szVarname, long* pHandle)
{
    if(pHandle == NULL || pServerAddr == NULL)
        return E_POINTER;

    unsigned long read = 0;

    long nErr =
        AdsSyncReadWriteReyEx2(port, pServerAddr, ADSIGRP_SYM_HNDBYNAME, 0x0,
        sizeof(long), pHandle, strlen(szVarname), (char*)szVarname, &read);

    return nErr;
}

Handles for PLC variables are released again with AdsFreeVarHandle and AdsFreeVarHandleEx.
long AdsFreeVarHandle(AmsAddr* pServerAddr, long handle)
{
    return AdsSyncWriteReq(pServerAddr, ADSIGRP_SYM_RELEASEHND, 0,
                           sizeof(handle), &handle);
}

long AdsFreeVarHandleEx(long port, AmsAddr* pServerAddr, long* pHandle)
{
    return AdsSyncWriteReqEx(port, pServerAddr, ADSIGRP_SYM_REALEASEHND, 0,
                             sizeof(handle), &handle);
}

The following two methods write the PLC variable '.switch' and in doing so set the speed to slow or fast.
long SpeedSlowEx(long port, AmsAddr* pServerAddr)
{
    // Handle der SPS-Variable ".switch" erstellen.
    long handleSpeedSlow = 0;
    long adserror = AdsGetVarHandleEx(port, pServerAddr, ".switch", &handleSpeedSlow);

    // Die SPS-Variable ".switch" auf FALSE setzen
    bool datafalse = false;
    adserror = AdsSyncWriteReqEx(port, pServerAddr, ADSIGRP_SYM_VALBYHND,
                                 handleSpeedSlow, 0x1, &datafalse);

    // Handle der SPS-Variable ".switch" freigeben
    adserror = AdsFreeVarHandleEx(port, pServerAddr, handleSpeedSlow);
    return adserror;
}

long SpeedFastEx(long port, AmsAddr* pServerAddr)
{
    // Handle der SPS-Variable ".switch" erstellen.
    long handleSpeedFast = 0;
    long adserror = AdsGetVarHandleEx(port, pServerAddr, ".switch", &handleSpeedFast);

    // Die SPS-Variable ".switch" auf TRUE setzen
    bool datatrue = true;
    adserror = AdsSyncWriteReqEx(port, pServerAddr, ADSIGRP_SYM_VALBYHND,
                                 handleSpeedFast, 0x1, &datatrue);

    // Handle der SPS-Variable ".switch" freigeben
    adserror = AdsFreeVarHandleEx(port, pServerAddr, handleSpeedFast);
    return adserror;
}

In the connect method, a connection to the variables is created in the PLC.
long connect (long port, AmsAddr* pServerAddr, PAdsNotificationFuncEx Callback)
{
    // Attribute der Notification festlegen
    AdsNotificationAttrib attr;
    attr.cbLength = 2;
    attr.nTransMode = ADSTRANS_SERVERCYCLE;
    attr.nMaxDelay = 100000000; // = 1 sec
    attr.nCycleTime = 100000; // = 0,5 sec

    // Handles der SPS-Variablen holen

    long adserr = AdsGetVarHandleEx(port, addr, ".engine", &hEngine);

    if(adserr == 0)
        adserr = AdsGetVarHandleEx(port, addr, ".deviceUp", &hDeviceUp);

    if(adserr == 0)
        adserr = AdsGetVarHandleEx(port, addr, ".deviceDown", &hDeviceDown);

    if(adserr == 0)
        adserr = AdsGetVarHandleEx(port, addr, ".steps", &hSteps);

    if(adserr == 0)
        adserr = AdsGetVarHandleEx(port, addr, ".count", &hCount);

    if(adserr == 0)
        adserr = AdsGetVarHandleEx(port, addr, ".switch", &hSwitch);

    // Überwachung der SPS-Variablen initialisieren

    if(adserr == 0)
    {
        attr.cbLength = 1;
        adserr = AdsSyncAddDeviceNotificationReqEx(port, addr, ADSIGRP_SYM_VALBYHND, hEngine,
                                                   &attr, Callback, engine, &hEngineNotification);
    }
    if(adserr == 0)
    {
        attr.cbLength = 1;
        adserr = AdsSyncAddDeviceNotificationReqEx(port, addr, ADSIGRP_SYM_VALBYHND, hDeviceUp,
                                                   &attr, Callback, deviceUp, &hDeviceUpNotification);
    }
    if(adserr == 0)
    {
        attr.cbLength = 1;
        adserr = AdsSyncAddDeviceNotificationReqEx(port, addr, ADSIGRP_SYM_VALBYHND, hDeviceDown,
                                                   &attr, Callback, deviceDown, &hDeviceDownNotification);
    }
    if(adserr == 0)
    {
        attr.cbLength = 1
        adserr = AdsSyncAddDeviceNotificationReqEx(port, addr, ADSIGRP_SYM_VALBYHND, hSteps,
                                                   &attr, Callback, steps, &hStepsNotification);
    }
    if(adserr == 0)
    {
        attr.cbLength = 2;
        adserr = AdsSyncAddDeviceNotificationReqEx(port, addr, ADSIGRP_SYM_VALBYHND, hCount,
                                                   &attr, Callback, count, &hCountNotification);
    }
    if(adserr == 0)
    {
        attr.cbLength = 1;
        adserr = AdsSyncAddDeviceNotificationReqEx(port, addr, ADSIGRP_SYM_VALBYHND, hSwitch,
                                                   &attr, Callback, switchSpeed, &hSwitchNotification);
    }

    return adserr;
}

When disconnecting the Ads connection, the handles of the PLC variables must be released and the port closed.
long disconnect(long port, AmsAddr* addr)
{
    // Handles der SPS-Variablen freigeben
    AdsFreeVarHandleEx(port, addr, hEngine);
    AdsFreeVarHandleEx(port, addr, hDeviceUp);
    AdsFreeVarHandleEx(port, addr, hDeviceDown);
    AdsFreeVarHandleEx(port, addr, hSteps);
    AdsFreeVarHandleEx(port, addr, hCount);
    AdsFreeVarHandleEx(port, addr, hSwitch);

    // Notifications löschen
    AdsSyncDelDeviceNotificationReqEx(port, addr, hEngineNotification);
    AdsSyncDelDeviceNotificationReqEx(port, addr, hDeviceUpNotification);
    AdsSyncDelDeviceNotificationReqEx(port, addr, hDeviceDownNotification);
    AdsSyncDelDeviceNotificationReqEx(port, addr, hStepsNotification);
    AdsSyncDelDeviceNotificationReqEx(port, addr, hCountNotification);
    AdsSyncDelDeviceNotificationReqEx(port, addr, hSwitchNotification);

    // Kommunikationsport schließen
    AdsPortCloseEx(port);

    return 0;
}

6. Processing the source code

The headers are integrated first in the SilverlightForEmbeddedMachineSample.cpp file.

#include "pwinuser.h"
#include "xamlruntime.h"
#include "xrdelegate.h"
#include "xrptr.h"

#include "resource.h"

The declaration of the variables follows.
IXRDelegate<XRMouseButtonEventArgs>* clickdelegate;

UINT exitcode;

IXRVisualHostPtr   vhost;
IXRButtonBasePtr   butClose;
IXRRadioButtonPtr  radSpeedSlow;
IXRRadioButtonPtr  radSpeedFast;
IXRTextBlockPtr    txtDeviceDown;
IXRTextBlockPtr    txtDeviceUp;
IXRTextBlockPtr    txtCount;
IXRProgressBarPtr  prgSteps;

IXRStoryboardPtr   timelineDeviceDown;
IXRStoryboardPtr   timelineDeviceUp;
IXRStoryboardPtr   timelineEngine;

long port;
AmsAddr addr;

unsigned long TimerID;
DWORD EventID;
CRITICAL_SECTION cs;
long event_cnt;
long event_cntold;

void __stdcall Callback(AmsAddr* addr, AdsNotificationHeader* handler, unsigned long User);

The timer callback function checks the Ads connection and restores the connection if necessary.
VOID CALLBACK MyTimerProc(
    HWND hwnd,     // handle to window for timer messages
    UINT message,  // WM_TIMER message
    UINT idTimer,  // timer identifier
    DWORD dwTimer) // current system time
{
    if (event_cnt = event_cntold)
    {
        // Handels werden freigegen und der Port geschlossen
        disconnect(port, &addr);

        // Kommunikationsport auf dem ADS Router öffnen
        port = AdsPortOpenEx();

        addr.port = 0x321;
        long adserror = -1;

        // Neue Verbindung zur SPS herstellen.
        while(adserror != 0)
        {
            adserror = connect(port, &addr, Callback);
            Sleep(1000);
        }
    }

    event_cntold = event_cnt;
}

The following Ads Event handler is called if a PLC variable to which a link exists changes.

// ADS-State Callback-Function
void __stdcall Callback(AmsAddr* addr, AdsNotificationHeader* handler, unsigned long User)
{
    event_cnt++;

The corresponding arrows are coloured red or not, depending on whether deviceUp, deviceDown or engine is set to TRUE.

This effect can be improved still further by the use of timelines.

    if (User == deviceUp)
    {
        if (*(bool*)handler->data == true)
        {
            timelineDeviceDown->Stop();
            timelineEngine->Stop();
            timelineDeviceUp->Begin();
        }
    }
    else if (User == deviceDown)
    {
        if(*bool*)handler-data == true)
        {
            timelineDeviceUp->Stop();
            timelineEngine->Stop();
            timelineDeviceDown->Begin();
        }
    }
    else if (User == engine)
    {
        if (*(bool*)handler->data == true)
        {
            timelineDeviceDown->Stop();
            timelineDeviceUp->Stop();
            timelineEngine->Begin();
        }
    }

steps indicates the number of cycles. The value is output via the progress bar prgSteps. For this the data must first be converted to a byte, since the associated PLC variable is of the type byte. Since the progress bar can only transfer data of the type float, conversion to the type float subsequently takes place.
    else if (User == steps)
    {
        prgSteps->SetValue((float)*((byte*)handler->data));
    }

In the case of count, as with steps, the data must first be converted to their original data type before they can be converted into a text and transferred to the text block txtCount.
    else if (User == count)
    {
        WCHAR text[6];
        wsprintf(text, L"%d", *(unsigned short*)handler->data);
        txtCount->SetText(text);
    }

The speed type is output via radio buttons. The appropriate radio button is marked depending on the speed.
    else if (User == switchSpeed)
    {
        if (*(bool*)handler->data == true)
        {
            radSpeedFast->SetIsChecked(XRThreeState_Checked);
        }
        else
        {
            radSpeedSlow->SetIsChecked(XRThreeState_Checked);
        }
    }
}

The OnClick event is triggered by various instances and the initiating instance can be distinguished from the name.

class BtnEventHandler
{
public:

    HRESULT OnClick(IXRDependencyObject* source, XRMouseButtonEventArgs* args)
    {
        BSTR name;
        HRESULT hr = NULL;
        source->GetName(&name);

        short state = 0;

        long adserror = 0;

        if wcscmp(name, L"butClose") == 0)
        {
            // Machine Dialog schließen
            vhost->EndDialog(exitcode);
        }
        if wcsmp(name, L"radSpeedSlow") == 0)
        {
            // Aufruf der Methode SpeedSlowEx um die Geschwindigkeit auf langsam zu setzen.
            adserror = SpeedSlowEx(port, &addr);
        }
        if wscmp(name, L"radSpeedFast") == 0)
        {
            // Aufruf der Methode SpeedFastEx um die Geschwindigkeit auf schnell zu setzen.
            adserror = SpeedFastEx(port, &addr);
        }

        if (adserror != NULL)
        {
            // Die Handels werden freigegen und der Port geschlossen
            disconnect(port, &addr);

            // Der Kommunikationsport auf dem ADS-Router wird geöffnet
            port = AdsPortOpenEx();

            addr.port = 0x321;

            // Neu Verbindung zur SPS herstellen.
            adserror = connect(port, &addr, Callback);

            Sleep(1000);
        }

        SysFreeString(name);
        return S_OK;
    }
};

In the WinMain method, the XMAL runtime must be initialised first. If XamlRuntimeInitialize is successful, then Silverlight for Windows Embedded Runtime is started in the application.

int WINAPI WinMain(HINSTANCE hInstance
                  HINSTANCE hPrevInstance,
                  LPTSTR lpCmdLine,
                  int nCmdShow)
{
    // Initialisierung der XAML Runtime
    if (!XamlRuntimeInitialize())
        return -1;

Each Silverlight for Windows Embedded application has its own "applications" object, via which access to global characteristics is possible.
The GetXRApplicationInstance API is used in order to access this object.
    HRESULT retcode;

    // Load an dinit XAML resource
    IXRApplicationPtr app;

    if (FAILED (retcode=GetXRApplicationInstance(&app)))
        return -1;

    if (FAILED (retcode=app->AddResourceModule(hInstance)))
        return -1;

After the initialisation of the application object, the main window can be created and the administration of the object can be handed over to Silverlight for Windows Embedded.
    XRWindowCreateParams wp;

    ZeroMemory(&wp, sizeof(XRWindowCreateParams));

    // Set window styles
    wp.Style = WS_BORDER;
    wp.pTitle = L"Silverlight for Windows Embedded Machine Sample";
    wp.Left = 0;
    wp.Top = 0;
    wp.AllowsMultipleThreadAccess = true;

    XRXamlSource xamlsrc;

    xamlsrc.SetResource(hInstance, TEXT("XAML"), MAKEINTRESOURCE(IDR_XAML1));

    if (FAILED(retcode=app->CreateHostFromXaml(&xamlsrc, &wp, &vhost)))
        return -1;

The object within a Silverlight for Windows Embedded application is organised in object trees. In order to access this object, a pointer to the root element is required.
    IXRFrameworkElementPtr root;

    if (FAILED (retocde=app->CreateHostFromXaml(&xamlsrc, &wp, &vhost)))
        return -1;

Creation of instances of the controls and timelines.
    // Get controls by name
    if (FAILED(retcode=root->FindName(TEXT("butClose"), &butClose)))
        return -1;

    if (FAILED(retcode=root->FindName(TEXT("radSpeedSlow", &radSpeedSlow)))
        return -1;

    if (FAILED(retcode=root->FindName(TEXT("radSpeedFast", &radSpeedFast)))
        return -1;

    if (FAILED(retcode=root->FindName(TEXT("txtDeviceDown", &txtDeviceDown)))
        return -1;

    if (FAILED(retcode=root->FindName(TEXT("txtDeviceUp", &txtDeviceUp)))
        return -1;

    if (FAILED(retcode=root->FindName(TEXT("txtCount", &txtCount)))
        return -1;

    if (FAILED(retcode=root->FindName(TEXT("prgSteps", &prgSteps)))
        return -1;

    // Get timelines by name
    if (FAILED (retcode=root->FindName(TEXT("timelineDeviceDown"), &timelineDeviceDown)))
        return -1;

    if (FAILED (retcode=root->FindName(TEXT("timelineDeviceUp"), &timelineDeviceUp)))
        return -1;

    if (FAILED (retcode=root->FindName(TEXT("timelineEngine"), &timelineEngine)))
        return -1;

Creating the "RadioButtonGroup" and assigning the two radio buttons from this group.
    WCHAR groupName[17];
    wsprintf(groupName, L"RadioButtonGroup");
    radSpeedFast->SetGroupName(groupName);
    radSpeedSlow->SetGroupName(groupName);

A redirecting object is needed to link the EventHandler with the buttons.
    BtnEventHandler handler;

    // Set the event handler for the buttons
    if (FAILED(retcode=CreateDelegate(&handler, &BtnEventHandler::OnClick, &clickdelegate)))
        return -1;

    if (FAILED(retcode=butClose->btnAddClickEventHandler(clickdelegate)))
        return -1;

    if (FAILED(retcode=radSpeedSlow->AddClickEventHandler(clickdelegate)))
        return -1;

    if (FAILED(retcode=radSpeedFast->AddClickEventHandler(clickdelegate)))
        return -1;

Integrating the Ads components.
    long adserror = -1;
    port = AdsPortOpenEx();

    AdsGetLocalAddressEx(port, &addr);

    // connect to the PLC and register callbacks
    addr.port = 0x321;
    adserror = connect(port, &addr, Callback);

    event_cnt = 0;
    event_cntold = -1;

    // init timer for reconnect
    SetTimer(NULL, NULL, 5000, MyTimerProc);

    if (FAILED(retcode=vhost->StartDialoge(&exitcode)))
        return -1;

    // cleanup
    disconnect(port, &addr);
    clickdelegate->Release();

    return 0;
}

7. Features

A connection to xamlruntime.lib and TcAdsDllCe.lib must be made in the project features.

Download Silverlight for Windows Embedded example

Sample Silverlight for Windows Embedded