以太网通信基本上最先想到的是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 0
82 64
-114 -34
-20 20
21 123
-89 65
-116 4
123 81
20 -20
82 0
-114 0
-20 0
21 0
82 64
-114 -34
-20 20
21 123
-89 65
-116 4
123 81
20 -20
82 0
-114 0
-20 0
21 0
123 64
20 -34
-34 20
64 123
-20 65
81 4
4 81
65 -20
0 0
0 0
-34 0
64 0
就像这样的。还有一个问题就是,当第一个数只要是浮点就会发生这样的情况,但是换成第一个是整型,第二个数是浮点,问题就不会发生了。所以这样的通信是不能让人接受的。
OPC方式需要直接在PLC组态的时候就要和Windows的OPC进行OPC组态,连接成功就能通信。
需要通过vs读取windows opc中的数据,查资料知道MatrikonOPC可以简单的和VS搭配
这个需要提前安装MatrikonOPCSimulation工具,默认路径安装就行。这是代码:
#import "C://Windows//SysWOW64//OPCAuto.dll"
#pragma warning( disable : 4786 )
#include
#include
#include
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 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 errs;
int i;
_variant_t value;
long err;
// VC数组索引从0开始,而在OPCAuto.dll需要中从1开始,所以是rgsabound[ 0 ].cElements = 4,而给pServerHandles赋值的时候应该给索引是1,2,3相应的赋值Server Handle
rgsabound[0].cElements = 4;
rgsabound[0].lLbound = 0;
pServerHandles = SafeArrayCreate(VT_I4, 1, rgsabound); //构建一个1维数组,类型是VT_I4
for (i = 0; i < opcItems.size(); i++) {
svrHdl = opcItems[i]->ServerHandle;
dim[0] = i + 1;
// 给数组的每个元素赋值,对应的索引值是1, 2, 3
SafeArrayPutElement(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是西门子提供的库,用来和vs进行通信。现在我能找到最新的是6.2版本,需要安装prodave安装包,并注册激活。
安装完成目录下会生成VB、VC的demo,但是VC的demo需要改一些东西才能在C++中编译成功。
Prodave6.2 密码:yak3
授权 密码:qr7b
我改的demo 密码:c8ce