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 .
- 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.
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
- pre-numbered, so that the same port is always used for this module instance.
- kept customizable, in order to offer several module instances the option to have their own ADS port assigned on startup of the TwinCAT system.
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.
- The server part is completed - continue with ADS client sends the ADS messages.