Sample03: TC3 ADS Server written in C++

This article describes how to create a TC3-C++ module acting as a ADS-server.
The server will provide an ADS interface to start / stop / reset an counter variable insight the C++ module.

Download

Here you can access the source code for this sample.

1. Unpack the downloaded ZIP file.
2. Using a Visual Studio with TwinCAT installed, open the project via Open Project ....
3. Configure signing for this project by switching on TwinCAT signing with a right-click on Project->Properties->Tc Sign and configure your certificate and password if necessary.
For more information on signing C++ projects, click here.
4. Select your target system.
5. Build the sample (e.g. Build->Build Solution).
6. Activate the configuration by clicking on Sample03: TC3 ADS Server written in C++ 1:.
The sample is ready for operation.

Description

This sample contains a C++ module that acts as an ADS server. The server grants access to a counter that can be started, stopped and read.

The header file of the module defines the counter variable m_bCount, and the corresponding .cpp file initializes the value in the constructor and implements the logic in the CycleUpdate method.

The AdsReadWriteInd method in the .cpp file analyzes the incoming messages and returns the return values. A define in the header file is added for a further added message type.

Details such as the definition of the ADS message types are described in the following cookbook, where you can compile the sample manually.

Cookbook

This is a step by step description about the creation of the C++ module.

1. Create a new TwinCAT 3 project solution

Follow the steps for creating a new TwinCAT 3 project.

2. Create a C++ project with ADS port

Follow the steps for the creation of a new TwinCAT 3 C++ project.

Select TwinCAT Module Class with ADS port in the Class templates dialog.

3. Add the sample logic to the project

1. Open the header file <MyClass>.h (in this sample Module1.h) and add the counter m_bCount to the protected area as a new member variable:
class CModule1
    : public ITComObject
    , public ITcCyclic
    ,...
{
public:
    DECLARE_IUNKNOWN()
    ....
protected:
    DECLARE_ITCOMOBJECT_SETSTATE();
///<AutoGeneratedContent id="Members">
    ITcCyclicCallerInfoPtr m_spCyclicCaller;
    .....
///</AutoGeneratedContent>
    ULONG m_ReadByOidAndPid;
    BOOL m_bCount;
};
2. Open the class file <MyClass>.cpp (in this sample Module1.cpp) and initialize the new values in the constructor:
CModule1::CModule1()
    .....
{
    memset(&m_Counter, 0, sizeof(m_Counter));
    memset(&m_Inputs, 0, sizeof(m_Inputs));
    memset(&m_Outputs, 0, sizeof(m_Outputs));
    m_bCount = FALSE; // by default the counter should not increment
    m_Counter = 0;    // we also initialize this existing counter
}
The sample code has been added.

3.a. Add the sample logic to the ADS server interface.

Usually, the ADS server receives an ADS message, which contains two parameters (indexGroup and indexOffset) and perhaps further data pData.

Designing an ADS interface
Our counter is to be started, stopped, reset, overwritten with a value or send a value to the ADS client on request:

indexGroup

indexOffset

Description

0x01

0x01

m_bCount = TRUE, counter is incremented.

0x01

0x02

Counter value is transferred to ADS client.

0x02

0x01

m_bCount = FALSE, counter is no longer incremented.

0x02

0x02

Reset counter.

0x03

0x01

Overwrite counter with value transferred by ADS client.

These parameters are defined in modules1Ads.h – change the source code to add a new command for IG_RESET.

#include "TcDef.h"
enum Module1IndexGroups : ULONG
{
    Module1IndexGroup1 = 0x00000001,
    Module1IndexGroup2 = 0x00000002, // add command
    IG_OVERWRITE = 0x00000003 // and new command
};

enum Module1IndexOffsets : ULONG
{
    Module1IndexOffset1 = 0x00000001,
    Module1IndexOffset2 = 0x00000002
};

Change the source code in your <MyClass>::AdsReadWriteInd() method (in this case in Module1.cpp).

switch(indexGroup)
{
case Module1IndexGroup1:
    switch(indexOffset)
    {
    case Module1IndexOffset1:
        ...
        // TODO: add custom code here
        m_bCount = TRUE; // receivedIG=1 IO=1, start counter
        AdsReadWriteRes(rAddr, invokeId, ADSERR_NOERR, 0,NULL);
        break;
    case Module1IndexOffset2:
        ...
        // TODO: add custom code here
        // map counter to data pointer
        pData = &m_Counter;     // received IG=1 IO=2, provide counter value via ADS
     AdsReadWriteRes(rAddr, invokeId, ADSERR_NOERR, 4 ,pData);
//comment this: AdsReadWriteRes(rAddr, invokeId,ADSERR_NOERR, 0, NULL);
     break;
    }
    break;
case Module1IndexGroup2:
    switch(indexOffset)
    {
    case Module1IndexOffset1:
     ...
     // TODO: add custom code here
    // Stop incrementing counter
    m_bCount = FALSE;
     // map counter to data pointer
    pData = &m_Counter;
     AdsReadWriteRes(rAddr, invokeId, ADSERR_NOERR, 4,pData);
     break;
case Module1IndexOffset2:
    ...
    // TODO: add custom code here
    // Reset counter
    m_Counter = 0;
    // map counter to data pointer
    pData = &m_Counter;
     AdsReadWriteRes(rAddr, invokeId, ADSERR_NOERR, 4, pData);
     break;
}
break;
case IG_OVERWRITE:
    switch(indexOffset)
    {
    case Module1IndexOffset1:
    ...
     // TODO: add custom code here     // override counter with value provided by ADS-client
     unsigned long *pCounter = (unsigned long*) pData;
     m_Counter = *pCounter;
     AdsReadWriteRes(rAddr, invokeId, ADSERR_NOERR, 4, pData);
     break;
}
break;
}
break;
default:
    __super::AdsReadWriteInd(rAddr, invokeId, indexGroup,indexOffset, cbReadLength, cbWriteLength, pData;
     break;
}

3.b. Add the sample logic to the cyclic part

The <MyClass>::CycleUpdate() method is cyclically called – this is the point where the logic is to be changed.

// TODO: Replace the sample with your cyclic code
m_Counter+=m_Inputs.Value; // replace this line
m_Outputs.Value=m_Counter;

In this case the counter mCounter is incremented if the boolean variable m_bCount is TRUE.

Insert this IF case into your cyclic method.

HRESULT CModule1::CycleUpdate(ITcTask* ipTask,
ITcUnknown* ipCaller, ULONG context)
{
HRESULT hr = S_OK;
// handle pending ADS indications and confirmations
CheckOrders();
....
// TODO: Replace the sample with your cyclic code
if (m_bCount)    // new part
{
    m_Counter++;
}
m_Outputs.Value=m_Counter;
}

4. Execute server sample

1. Run the TwinCAT TMC Code Generator in order to provide the inputs/outputs for the module.
2. Save the project.
3. Compile the project.
4. Create a module instance.
5. Create a cyclic task and configure the C++ module for the execution in this context.
6. Scan the hardware IO and assign the symbol Value of outputs to certain output terminals (this is optional).
7. Activate the TwinCAT project.
The sample is ready for operation.

5. Determine the ADS port of the module instance

Generally the ADS port may be

In this sample the default setting (keep flexible) is selected. First of all you have to determine the ADS port that was assigned to the module that has just been activated.

1. Navigate to the module instance.
2. Select the Parameter Online tab.
0x8235 or decimal 33333 is assigned to the ADS port (this may be different in your sample). If more and more instances are created, each instance is allocated its own unique AdsPort.
The counter is still at "0" because the ADS message to start the incrementation has not been sent.
Sample03: TC3 ADS Server written in C++ 2:
The server part is completed - continue with ADS client sends the ADS messages.