介绍

TwinCAT3

TwinCAT3是Beckhoff推出的一款基于PC的控制器软件,简单理解是一套集成开发环境,里边有各种分析工具以及通信中间件;开发者可以很方便的用它来进行IPC和PLC之间的通信连接

ADS

倍福ADS(‌Automation Device Specification)‌是一个跨设备的网络通信协议(基于TCP/IP),‌依托TwinCAT提供的通信组件,它允许PLC、IPC之间进行数据分发、读写,支持同步、异步通信方式

环境搭建

VS2019

安装C++桌面开发组件

倍福ADS通信教程_QtADS

安装完Visual Studio版本信息页面如下

倍福ADS通信教程_工业自动化_02

TwinCAT3

在使用ADS之前需要先安装TWinCAT3环境,我这里装的是V3.1.4024.55,属于TwinCAT3 Full版本,下载页面: https://www.beckhoff.com.cn/en-en/download/650023470

TwinCAT3 Full版本分XAR和XAE两部分,需要搭配Visual Studio版本使用

  • XAE:eXtended Automation Engineerin,XAE是基于Visual Studio作为开发环境,进行多种语言的编程和硬件组态
  • XAR:eXtended Automation Runtime,XAR是实时运行环境,对 TwinCAT 模块加载、执行、管理、实时运行与调用

TwinCAT3安装比较简单,勾选相应的组件,默认安装就可以了

倍福ADS通信教程_QtADS_03

Qt6.7.2

使用在线工具安装Qt6.7.2,第一次打开需要使用--mirror命令指定软件源

qt-online-installer-windows-x64-4.8.0.exe  --mirror https://mirror.nju.edu.cn/qt

Qt组件勾选

倍福ADS通信教程_ADS_04

通信实验

操作流程

  1. VS2019 XAE新建PLC工程,配置工程,编译工程登入后可以查看PLC变量内存地址
  2. QtCreator新建C++ ADS Reader工程,根据PLC工程的IP和端口配置ADS连接
  3. QtCreator新建C++ ADS Writer工程,根据PLC工程的IP和端口配置ADS连接
  4. 在Reader进行数据读取操作,在Writer进行数据写入操作

PLC
编写PLC程序:定义BOOL、INT、REAL等3个变量类型数据

PROGRAM MAIN
VAR
    boolVar1 AT%M*:BOOL;
    intVar2 AT%M*:INT;
    realVar3 AT%M*:REAL;
END_VAR

编译生成PLC应用并登入

倍福ADS通信教程_ADS_05

打开Target Brownser查看变量内存地址分布

变量类型groupoffset
boolVar1BOOL0x40200x5DFF0
intVar2INT0x40200x5DFF2
realVar3REAL0x40200x5DFF4

PLC和C++之间变量大小对应关系

变量类型C++长度
BOOLbool1
INTint4
UINTunsigned int4
REALfloat4
LREALdouble8

Reader
建立ADS连接后读取指定地址(group + offset)的变量数据

#include <QCoreApplication>
#include <Windows.h>
#include "TcAdsDef.h"
#include "TcAdsAPI.h"

int main(int argc, char *argv[])
{
    long      ret, port;
    AmsAddr   addr;
    bool byte;

    port = AdsPortOpen();
    ret = AdsGetLocalAddress(&addr);
    if (ret)
    {
        qDebug() << "AdsGetLocalAddress : " << ret;
    }

    if (port == addr.port)
    {
        qDebug() << "LocalAdsPort: " << port << " opened!";
    }
    else
    {
        qDebug() << "LocalAdsPort open failed!";
    }

    addr.port = 851;

    int time = 0;
    int indexGroup = 0x4020;
    float var;

    while (1)
    {
        ret = AdsSyncReadReq(&addr, indexGroup, 0x5dff0, sizeof(bool), &byte);
        if(!ret) {
            qDebug() << "boolVar1 : " << byte;
        }

        ret = AdsSyncReadReq(&addr, indexGroup, 0x5dff2, sizeof(int), &time);
        if(!ret) {
            qDebug() << "intVar2 : " << time;
        }

        ret = AdsSyncReadReq(&addr, indexGroup, 0x5dff4, sizeof(float), &var);
        if(!ret) {
            qDebug() << "realVar3 : " << var;
        }
        Sleep(1000);
    }

    return 0;
}

Writer
建立ADS连接后往指定变量地址(group + offset)写入数据

#include <QCoreApplication>
#include <Windows.h>
#include "TcAdsDef.h"
#include "TcAdsAPI.h"

int main(int argc, char *argv[])
{
    long      ret, port;
    AmsAddr   addr;
    bool byte = false;

    port = AdsPortOpen();
    ret = AdsGetLocalAddress(&addr);
    if (ret)
    {
        qDebug() << "AdsGetLocalAddress : " << ret;
    }

    if (port == addr.port)
    {
        qDebug() << "LocalAdsPort: " << port << " opened!";
    }
    else
    {
        qDebug() << "LocalAdsPort open failed!";
    }

    addr.port = 851;

    int time = 0;
    int indexGroup = 0x4020;

    while (1)
    {
        byte = !byte;
        ret = AdsSyncWriteReq(&addr, indexGroup, 0x5dff0, sizeof(bool), &byte);

        time++;
        ret = AdsSyncWriteReq(&addr, indexGroup, 0x5dff2, sizeof(int), &time);

        float var = time * 0.9 + 3.14;
        ret = AdsSyncWriteReq(&addr, indexGroup, 0x5dff4, sizeof(float), &var);
        Sleep(1000);
    }

    return 0;
}

CMakeLists.txt

两个工程的CMake写法参考:需要include几个头文件(afxstr.h、TcAdsAPI.h、TcAdsDef.h、wingdi.h),然后链接TcAdsDll这个库

cmake_minimum_required(VERSION 3.14)

project(QtADS LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)

include_directories(${CMAKE_SOURCE_DIR}/Include)

link_directories(${CMAKE_SOURCE_DIR}/lib)

add_executable(QtADS
main.cpp
)
target_link_libraries(QtADS Qt${QT_VERSION_MAJOR}::Core TcAdsDll)

include(GNUInstallDirs)
install(TARGETS QtADS
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

接口说明

  • 获取库版本号:AdsGetDllVersion
  • 打开通信端口:ADSPortOpen
  • 关闭通信端口:AdsPortClose
  • 获取本机地址:AdsGetLocalAddress
  • 获取错误信息:AdsGetLastError
  • 同步写:AdsSyncWriteReq
  • 同步读:AdsSyncReadReq
  • 设备通知回调:AdsSyncAddDeviceNotificationReq

开源库

介绍

Qt封装的 ADS组件也能很方便地进行ADS通信,支持QADSBOOL、QADSDINT、QADSDWORD、QADSENUM、QADSLREAL、QADSSTRING及对应的数组类型,所有数据类型都要持有通信的网络端口信息、节点信息,都要定义相应的value、setValue方法,但目前还没有支持Qt6,但改一改的话也可以用,需要进行两处改动:

① 将QString::SkipEmptyParts改为Qt::SkipEmptyParts

QString::SkipEmptyParts

改为

Qt::SkipEmptyParts

② QByteArray insert成员报错时需要把成员变成QByteArray兼容类型,通过toLatin1函数来转换

QByteArray sendValue = QByteArray(adsSymbolSize(),'\0');
sendValue.insert(0,val);

改为

QByteArray sendValue = QByteArray(adsSymbolSize(),'\0');
sendValue.insert(0,val.toLatin1());

示例

以BOOL类型数据访问为例:QADSBOOL通过配置端口、网络信息、节点名称等信息来访问节点

QADSBOOL *value = new QADSBOOL(this, 851, "local", "localhost", "MAIN.bTestVar1", QADSPLCVariable::ON_DEMAND, 0);

QtADS库封装了value、setValue等接口来进行读写操作

value->value();
value->setValue(false);

参考

【1】 https://tr.beckhoff.com.cn/pluginfile.php/44857/mod_resource/content/0/ADS高级培训.pdf

【2】 https://tr.beckhoff.com.cn/mod/folder/view.php?id=2058
【3】 https://github.com/Framatome/QtADS