一、TCPIP通信
以太网通信基本上最先想到的是TCPIP
就是在PC端的VS上布置服务器端,我用的是VS2015,最长用的是WinSock2.h
大致的步骤就是:
a.初始化版本号
b.建立套接字
c.定义并绑定地址
d.进入监听状态
e.接收连接请求
f.交换数据
有一点值得注意的是,Inter的CPU和西门子的CPU的储存方式不同,大端和小端储存方式。就是你VS定义的一个int型数据,在传输的时候char[0]是高位,而西门子的储存方式是char[3]是高位。这个也是做了大量的实验发现的问题,后来在西门子提供的prodave库中的函数也得到了证明。
send_text[0] = temp_send[3];
send_text[1] = temp_send[2];
send_text[2] = temp_send[1];
send_text[3] = temp_send[0];
转换以后再发送,数就能对的上了,但是还有问题。
做实验的时候发现,可能PLC通信的时候还需要时序的信息,还有一些不为人知的数据结构,这就导致PC发送的时候,PLC收到的是不含时序或者混乱的信息。比如我发送三个数,一个浮点一个整型,一个bool型给PLC,PLC的DB块负责接收,发现第1次接收是正确的,第2次就错了,第3次错,第4次错,第5次错,第6次正确。。。就是这样的情况。
PLC PC
123 64
20 -34
-34 20
64 123
-20 65
81 4
4 81
65 -20
0 0
0 0
-34 0
64 0-89 64
-116 -34
123 20
20 123
82 65
-114 4
-20 81
21 -20
-89 0
-116 0
123 0
20 0-20 64
81 -34
4 20
65 123
123 65
20 4
-34 81
64 -20
0 0
0 0
4 0
65 082 64
-114 -34
-20 20
21 123
-89 65
-116 4
123 81
20 -20
82 0
-114 0
-20 0
21 082 64
-114 -34
-20 20
21 123
-89 65
-116 4
123 81
20 -20
82 0
-114 0
-20 0
21 0123 64
20 -34
-34 20
64 123
-20 65
81 4
4 81
65 -20
0 0
0 0
-34 0
64 0
就像这样的。还有一个问题就是,当第一个数只要是浮点就会发生这样的情况,但是换成第一个是整型,第二个数是浮点,问题就不会发生了。所以这样的通信是不能让人接受的。
二、OPC方式
OPC方式需要直接在PLC组态的时候就要和Windows的OPC进行OPC组态,连接成功就能通信。
需要通过vs读取windows opc中的数据,查资料知道MatrikonOPC可以简单的和VS搭配
这个需要提前安装MatrikonOPCSimulation工具,默认路径安装就行。这是代码:
#import "C://Windows//SysWOW64//OPCAuto.dll"
#pragma warning( disable : 4786 )
#include <comdef.h>
#include <vector>
#include <iostream>using namespace std;using namespace OPCAutomation;//声明全局变量
typedef struct OLEInit {OLEInit() { CoInitialize(NULL); }~OLEInit() { CoUninitialize(); }
} OLEInit;OLEInit oleInit;
OPCAutomation::IOPCAutoServerPtr opcSvr;
OPCAutomation::IOPCGroupsPtr opcGrps;
OPCAutomation::IOPCGroupPtr opcGrp;
vector<OPCAutomation::OPCItemPtr> opcItems;//连接到OPC Server
void agOPConn(const char * opcSvrName)
{HRESULT hr;hr = opcSvr.CreateInstance(_uuidof(OPCServer));if (FAILED(hr)){cerr << "OPCSever CreateInstance failed, hr = " << hr << endl;exit(1);}opcSvr->Connect(opcSvrName);
}//断开连接void agOPCDisc()
{opcGrps->RemoveAll();opcSvr->Disconnect();
}//创建一个组void agOPCCreateGroup()
{opcGrps = opcSvr->OPCGroups;opcGrp = opcGrps->Add(_variant_t("group1"));
}void agOPCAddItems()
{OPCItemPtr opcItm;opcItm = opcGrp->OPCItems->AddItem(_bstr_t("Bucket Bridge.Int4"),1);opcItems.push_back(opcItm);opcItm = opcGrp->OPCItems->AddItem(_bstr_t("Bucket Bridge.Int2"), 1);opcItems.push_back(opcItm);opcItm = opcGrp->OPCItems->AddItem(_bstr_t("Bucket Bridge.String"), 1);opcItems.push_back(opcItm);}//显示读取的值void agDumpVariant(VARIANT *v)
{switch (v->vt){case VT_I2:printf("value(VT_I2) = %d ", v->iVal);break;case VT_I4:printf("value(VT_I4) = %ld", v->lVal);break;case VT_BSTR:printf(" value(VT_BSTR) = %ls ", v->bstrVal);break;default:printf(" value(unknown type:%d) ", v->vt);break;}
}//同步读取三个Item的值,同步在很多情况下都是简单有效的选择方案,其实读取的异步方式在C++中可以建立一个工作线程来执行同步读的操作,等有新的Item值的时候再通过某种线程间通信的方式告诉主线程“数据改变”的事件
void agOPCReadItems() {_variant_t quality;_variant_t timestamp;SAFEARRAY *pServerHandles;SAFEARRAY *pValues;SAFEARRAY *pErrors;SAFEARRAYBOUND rgsabound[1];long dim[1];long svrHdl;vector<_variant_t> values;vector<long> errs;int i;_variant_t value;long err;// VC数组索引从0开始,而在OPCAuto.dll需要中从1开始,所以是rgsabound[ 0 ].cElements = 4,而给pServerHandles赋值的时候应该给索引是1,2,3相应的赋值Server Handlergsabound[0].cElements = 4;rgsabound[0].lLbound = 0;pServerHandles = SafeArrayCreate(VT_I4, 1, rgsabound); //构建一个1维数组,类型是VT_I4for (i = 0; i < opcItems.size(); i++) {svrHdl = opcItems[i]->ServerHandle;dim[0] = i + 1;// 给数组的每个元素赋值,对应的索引值是1, 2, 3SafeArrayPutElement(pServerHandles, dim, &svrHdl);}opcGrp->SyncRead(OPCDevice,3, // 读取的Item数目&pServerHandles, // 输入的服务器端句柄数组&pValues, // 输出的Item值数组&pErrors, // 输出的Item错误状态数组&quality, // 读取的值的状态×tamp); // 读取的事件戳for (i = 1; i <= opcItems.size(); i++) {dim[0] = i;SafeArrayGetElement(pValues, dim, &value); // 读取Item值在value中SafeArrayGetElement(pErrors, dim, &err); // 读取错误状态值在err中 values.push_back(value);errs.push_back(err);}for (i = 0; i < values.size(); i++) {agDumpVariant(&values[i]); // 显示读取的Item值cout << ", err = " << errs[i] << endl;}SafeArrayDestroy(pServerHandles);SafeArrayDestroy(pValues);SafeArrayDestroy(pErrors);
}// 写入3个Item的值,为了演示实例简单,参数传递3个对应的Item值
void agOPCWriteItems(vector<_variant_t> values) {_variant_t quality;_variant_t timestamp;SAFEARRAY *pServerHandles;SAFEARRAY *pValues;SAFEARRAY *pErrors;long dim[1];long svrHdl;int i;SAFEARRAYBOUND rgsabound[1];rgsabound[0].cElements = values.size() + 1;rgsabound[0].lLbound = 0;pServerHandles = SafeArrayCreate(VT_I4, 1, rgsabound);pValues = SafeArrayCreate(VT_VARIANT, 1, rgsabound);for (i = 0; i < values.size(); i++) {svrHdl = opcItems[i]->ServerHandle;dim[0] = i + 1;SafeArrayPutElement(pServerHandles, dim, &svrHdl);SafeArrayPutElement(pValues, dim, &values[i]);}opcGrp->SyncWrite(3, &pServerHandles, &pValues, &pErrors);SafeArrayDestroy(pServerHandles);SafeArrayDestroy(pValues);SafeArrayDestroy(pErrors);
}//main主程序
int main()
{try{agOPConn("Matrikon.OPC.Simulation.1");agOPCCreateGroup();agOPCAddItems();// 第一次写和读vector<_variant_t> values;values.push_back((long)156);values.push_back((short)11);values.push_back("opc");agOPCWriteItems(values);agOPCReadItems();cout << "---------------------------------------" << endl;// 第二次写和读vector<_variant_t> values1;values1.push_back((long)123456);values1.push_back((short)666);values1.push_back("hello");agOPCWriteItems(values1);agOPCReadItems();}catch (_com_error &e) {// 应该在上面的子函数里面捕捉异常,但为了演示简单,在主函数里面捕捉异常_bstr_t bstrSource(e.Source());_bstr_t bstrDescription(e.Description());cout << "Code = " << e.Error() << endl;cout << "Code meaning = " << e.ErrorMessage() << endl;cout << "Source = " << (LPCTSTR)bstrSource << endl;cout << "Description = " << (LPCTSTR)bstrDescription << endl;}system("pause");getchar();return 0;
}
因为网上的代码import的时候加了no_namespace 但是得到的.tlh找不到OPCAuto.dll的底层函数接口,所以我就加上了,namespace问题解决。这样的通信方法,一组数大约需要100ms,可能不是因为数据量的问题,可能就是这样的速度。
三、prodave
prodave是西门子提供的库,用来和vs进行通信。现在我能找到最新的是6.2版本,需要安装prodave安装包,并注册激活。
安装完成目录下会生成VB、VC的demo,但是VC的demo需要改一些东西才能在C++中编译成功。
Prodave6.2 密码:yak3
授权 密码:qr7b
我改的demo 密码:c8ce