关于PC端与PLC端以太网通信

一、TCPIP通信

以太网通信基本上最先想到的是TCPIP

就是在PC端的VS上布置服务器端,我用的是VS2015,最长用的是WinSock2.h

大致的步骤就是:

      a.初始化版本号

      b.建立套接字

      c.定义并绑定地址

      d.进入监听状态

      e.接收连接请求

      f.交换数据

有一点值得注意的是,Inter的CPU和西门子的CPU的储存方式不同,大端和小端储存方式。就是你VS定义的一个int型数据,在传输的时候char[0]是高位,而西门子的储存方式是char[3]是高位。这个也是做了大量的实验发现的问题,后来在西门子提供的prodave库中的函数也得到了证明。

关于PC端与PLC端以太网通信_第1张图片

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方式

OPC方式需要直接在PLC组态的时候就要和Windows的OPC进行OPC组态,连接成功就能通信。

关于PC端与PLC端以太网通信_第2张图片

需要通过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

prodave是西门子提供的库,用来和vs进行通信。现在我能找到最新的是6.2版本,需要安装prodave安装包,并注册激活。

安装完成目录下会生成VB、VC的demo,但是VC的demo需要改一些东西才能在C++中编译成功。

Prodave6.2    密码:yak3

授权               密码:qr7b

我改的demo   密码:c8ce

你可能感兴趣的:(PC端与PLC端以太网通信,PC端与PLC端以太网通信)