嗨伙计们,由于个人学习工作等原因沉寂了大半年,忙碌好不容易暂告一段落,今天罗伯特祥终于又和大家见面了!

前段时间,有好多网友email我问ElmoPlatinum Maestro运动控制器中modbus通信的问题,今天呢就系统的跟大家一起聊聊这件事儿。(多图预警)

1. modbus的扯谈

首先明确Modbus是干啥的,它其实就是一种工业现场总线协议标准,属于应用层报文传输协议,包括ASCIIRTUTCP三种报文类型。对于连接在Modbus总线上的设备来说,可以分为主站(poll)和从站(slave)。在使用TCP通信时,主站为client端,主动建立连接;从站为server端,等待连接。(吐槽:这个关系对可真是太容易让人感到疑惑了!)

 

email中问我最多的是modbus是怎么实现通信的以及它与EtherCAT的区别,关于这个问题,我想大概是我们常用的伺服系统中一般驱动器与控制器走的EtherCAT总线的原因。

我们来简单捋一下,以Gold系列EtherCAT型驱动器和PMAS控制器为例,首先这就明确了驱动器与控制器的通信方式是EtherCAT,当然另一种常用的总线协议是CANOpen,这要看设备支持哪种协议;然后是运动控制器与上位机之间的通信,笔者个人最常用的是Modbus TCP,当然了在PMASTCP/IP也是支持的。那么现在它俩的区别你应该猜到了,EtherCAT总线主要是面向运动控制,因此更注重实时性,而Modbus TCP一般只用于常规IO和非实时数据的读取和操作,所以这就是它俩的主要区别。

 

对于modbus通信的调试,两款调试软件是必备选项:Modbus pollModbus slave,在ModbusTCP中,Modbus poll作为客户端请求数据,Modbus slave作为服务器端处理请求。我们在下文中呢,将使用Modbus poll来模拟上位机。

2. 数据存储问题

任何协议的使用,都绕不过数据包的填写,Modbus也一样。我们要明确一点,不管什么数据,它在内存中都是01的二进制量,只不过我们通过不同的方式把它读出来而已,这就涉及浮点数与整型数据在存储上的区别,感兴趣的老铁可以自行百度一波。

Modbus还涉及一个“寄存器”的概念,一个寄存器为8位,因此对于一个float型数据的存储,我们需要将它拆成4个寄存器来存储。好啵问题又来了,这里又会涉及一个大小端存储的问题和数据传输的字序问题,我们直接给出结论:Platinum Maestro运动控制器是基于ARM架构的,所以采用的小端存储方式。

PC通常是小端,所以不用担心大小端的转换问题。

接下来我们看下PMAS中关于Modbus数据结构体的定义。

typedef struct
{
	int startRef;
	int refCnt;
	short regArr[MODBUS_IPC_WRITE_VALUES];

}MMC_MODBUSWRITEHOLDINGREGISTERSTABLE_IN;

由以上定义可以看出,regArr数组是一个short(16),所以我们需要把float(32)的数拆成两个16位并转成short型去赋值给寄存器,而且还不能出现精度损失,换句话说就是需要在无精度损失的条件下把float型数据拆成216位。

这里就看你对指针的理解啦!

再啰嗦一句,在PMAS中我们可以简单认为这些一系列的Modbus寄存器是事先定义好的数组,Modbus数据的传输,我们通过读写这些数组实现,这些数组在PMAS中又被封装在响应的结构体当中,具体参加PMAS API手册。

注意:Platinum Maestro控制器的寄存器最多有131070个寄存器,能装65536float数据(索引号为0-65535)。

3. PMAS控制器中modbus的使用

  • Platinum Maestro运动控制器默认IP:192.168.1.3
  • 默认Port502

 

在安装完MDS IDE以后,安装目录 C:\GMAS\GMASSamples 下会有几个小案例, CPP_EthercatCyclicPosHome 这个项目中就有Modbus TCP的使用。

既然例程有了,我们今天的关注点就不放在使用上了,而是放在需要注意的点上:Modbus传输过程中的字序问题。

首先我们来查看Platinum Maestro的寄存器与模拟端寄存器的对应关系。

打开控制程序,单步执行到启动Modbus Server

然后打开Modbus Poll,添加多个寄存器:

连接Platinum Maestro

EAS II端的Maestro Setup and Motion选项的Modbus Configuration选项中添加PMAS中的寄存器显示区:

发现register[1]4:00000对应的寄存器,输入数之后,会自动消失,映射无效!!!

这说明在Platinum Maestro运动控制器中,地址起始索引为0对应的寄存器是无效的,Modbus Poll的地址索引为0的寄存器对应 Maestro地址起始索引为1对应的寄存器,并往后依次类推。

注意1:为了避免错误,不建议在Maestro从地址索引为0的寄存器开始使用,可能会造成数据错误的问题,但4:00001所对应的寄存器还是可以使用的,使用的时候需要避开与4:00000组成32位或64位数据,因为这样会造成数据丢失16位的现象)

注意2:在MDS中写控制器程序时,地址索引0对应的是EASII中的4:00001

对于long型数据来说, MaestroModbus寄存器中按大端的方式写的时候,Modbus Poll端需要用小端方式(Long CD AB)进行解析,而当Modbus Poll端用大端方式(Long AB CD)进行解析时,与MaestroModbus寄存器对应关系如下图,此时如果上位机按这种方式去解析的时候,那么Maestro中需要对数据存储顺序进行一个转换。

对于浮点数来说:与Long相同,如果控制器中的数据是按大端(float AB CD)来进行存储的(此处是指宏观上的大小端,也就是说你用两个16位的寄存器去表示一个32位的数的时候写的字序,而不是机器本身存储的方式),那么Modbus Poll就应该按小端(float CD AB)来解读;如果控制器中的数据是按小端(float CD AB)来进行存储的,那么Modbus Poll就应该按大端(float AB CD)来解读。

// PMAS读取PC对寄存器写入的数据
	mbus_read_in.startRef = 1; //Modbus读取地址从4:00001开始
	mbus_read_in.refCnt = 4;   //读4个地址

	MMC_MbusReadHoldingRegisterTable(gConnHndl, &mbus_read_in,&mbus_read_out);
	int pc_data1 = mbus_read_out.regArr[1]<<16 | mbus_read_out.regArr[0];
	int pc_data2 = mbus_read_out.regArr[3]<<16 | mbus_read_out.regArr[2];
	cout<<"pc_data1:"<<pc_data1<<"      pc_data2:"<<pc_data2<<endl;

弄清楚这一点对于保证你传输数据的正确性至关重要!!!!

对于Power PMAC控制器来说,也存在这个大端数据小端读、小端数据大端读的现象。

综上,PMASModbus Poll中字序对应关系如下:

最后上测试代码:

int main()
{

	MMC_CONNECT_HNDL gConnHndl ;					// Connection Handle
	CMMCConnection cConn ;
	gConnHndl = cConn.ConnectIPCEx(0x7fffffff,(MMC_MB_CLBK)CallbackFunc);
	cConn.RegisterEventCallback(MMCPP_MODBUS_WRITE,(void*)ModbusWrite_Received) ;

	MMC_MODBUSSTARTSERVER_IN mbus_startserver_in;
	MMC_MODBUSSTARTSERVER_OUT mbus_startserver_out;

	MMC_MODBUSISRUNNING_IN	mbus_IsRunningIn ;
	MMC_MODBUSISRUNNING_OUT	mbus_IsRunningOut ;

	MMC_MODBUSREADHOLDINGREGISTERSTABLE_IN 		mbus_read_in;
	MMC_MODBUSREADHOLDINGREGISTERSTABLE_OUT 	mbus_read_out;

	MMC_MODBUSWRITEHOLDINGREGISTERSTABLE_IN 	mbus_write_in;
	MMC_MODBUSWRITEHOLDINGREGISTERSTABLE_OUT 	mbus_write_out;

	MMC_MODBUSSTOPSERVER_IN mbus_stopserver_in;
	MMC_MODBUSSTOPSERVER_OUT mbus_stopserver_out;


	mbus_startserver_in.id = 1;  //Modbus start server enumrator ID

	int rc = MMC_MbusIsRunning (gConnHndl,&mbus_IsRunningIn,&mbus_IsRunningOut);
	//cout<<"modbus_status1:"<<rc<<endl;
	//启动Modbus Server
	MMC_MbusStartServer(gConnHndl,&mbus_startserver_in,&mbus_startserver_out);

	rc = MMC_MbusIsRunning (gConnHndl,&mbus_IsRunningIn,&mbus_IsRunningOut);
	//cout<<"modbus_status2:"<<rc<<endl;

	if(mbus_IsRunningOut.isrunning == 0)
	{
		MMC_MbusStartServer(gConnHndl,&mbus_startserver_in,&mbus_startserver_out);

		int rc_t = MMC_MbusIsRunning (gConnHndl,&mbus_IsRunningIn,&mbus_IsRunningOut);
		if(rc_t != 1)
			cout<<"modbus connect error!!!"<<endl;
	}
//------------------------------------------------------------------------------------

	mbus_read_in.startRef = 0; //Modbus读取地址
	mbus_read_in.refCnt = 4;   //读4个地址

	MMC_MbusReadHoldingRegisterTable(gConnHndl, &mbus_read_in,&mbus_read_out);
	short pc_data_temp1[2];
	short pc_data_temp2[2];
	pc_data_temp1[0] = mbus_read_out.regArr[1]; //CD  因为Modbus Poll是按大端发的(ABCD)
	pc_data_temp1[1] = mbus_read_out.regArr[0]; //AB
	pc_data_temp2[0] = mbus_read_out.regArr[3];
	pc_data_temp2[1] = mbus_read_out.regArr[2];

	float *pc_data1 = (float*)pc_data_temp1;
	float *pc_data2 = (float*)pc_data_temp2;
	cout<<"pc_data1:"<<*pc_data1<<"      pc_data2:"<<*pc_data2<<endl;

	int pmas_data1 = 8;
	int pmas_data2 = 16;

	mbus_write_in.startRef = 4;
	mbus_write_in.refCnt = 4;

	InsertLongVarToModbusShortArr(&mbus_write_in.regArr[0],(long) pmas_data1);
	InsertLongVarToModbusShortArr(&mbus_write_in.regArr[2],(long) pmas_data2);

	MMC_MbusWriteHoldingRegisterTable(gConnHndl, &mbus_write_in,&mbus_write_out);
//-------------------------------------------------------------------------------

	//关闭Modbus Server
	MMC_MbusStopServer(gConnHndl, &mbus_stopserver_in, &mbus_stopserver_out);
	MMC_CloseConnection(gConnHndl);
}

此外,对于上位机中Modbus的编程,建议使用libmodbus库,本文不再赘述该库的使用!

总结

  1. Modbus TCP通信中,主站为client端,从站为server端;
  2. PMAS中可简单认为Modbus寄存器是事先定义好的数组,Modbus数据的传输通过读写这些数组实现;
  3. PMAS端的CDAB对应Modbus Poll端的ABCDPMAS端的ABCD对应Modbus Poll端的CDAB
  4. PMAS中寄存器索引从0开始,Modbus Poll端从1开始,其他设备也应该确认是否存在此差异!

 

以上内容为笔者在工程实践过程中的个人总结,如有错误还请各位看官批评指教!

读万卷书也要行万里路,我是罗伯特祥,下次见~

参考文献: