嗨伙计们,由于个人学习工作等原因沉寂了大半年,忙碌好不容易暂告一段落,今天罗伯特祥终于又和大家见面了!
前段时间,有好多网友email我问Elmo的Platinum Maestro运动控制器中modbus通信的问题,今天呢就系统的跟大家一起聊聊这件事儿。(多图预警)
1. modbus的扯谈
首先明确Modbus是干啥的,它其实就是一种工业现场总线协议标准,属于应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。对于连接在Modbus总线上的设备来说,可以分为主站(poll)和从站(slave)。在使用TCP通信时,主站为client端,主动建立连接;从站为server端,等待连接。(吐槽:这个关系对可真是太容易让人感到疑惑了!)
在email中问我最多的是modbus是怎么实现通信的以及它与EtherCAT的区别,关于这个问题,我想大概是我们常用的伺服系统中一般驱动器与控制器走的EtherCAT总线的原因。
我们来简单捋一下,以Gold系列EtherCAT型驱动器和PMAS控制器为例,首先这就明确了驱动器与控制器的通信方式是EtherCAT,当然另一种常用的总线协议是CANOpen,这要看设备支持哪种协议;然后是运动控制器与上位机之间的通信,笔者个人最常用的是Modbus TCP,当然了在PMAS中TCP/IP也是支持的。那么现在它俩的区别你应该猜到了,EtherCAT总线主要是面向运动控制,因此更注重实时性,而Modbus TCP一般只用于常规IO和非实时数据的读取和操作,所以这就是它俩的主要区别。
对于modbus通信的调试,两款调试软件是必备选项:Modbus poll和Modbus 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型数据拆成2个16位。
这里就看你对指针的理解啦!
再啰嗦一句,在PMAS中我们可以简单认为这些一系列的Modbus寄存器是事先定义好的数组,Modbus数据的传输,我们通过读写这些数组实现,这些数组在PMAS中又被封装在响应的结构体当中,具体参加PMAS API手册。
注意:Platinum Maestro控制器的寄存器最多有131070个寄存器,能装65536个float数据(索引号为0-65535)。
3. PMAS控制器中modbus的使用
- Platinum Maestro运动控制器默认IP:192.168.1.3
- 默认Port:502
在安装完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型数据来说, Maestro在Modbus寄存器中按大端的方式写的时候,Modbus Poll端需要用小端方式(Long CD AB)进行解析,而当Modbus Poll端用大端方式(Long AB CD)进行解析时,与Maestro的Modbus寄存器对应关系如下图,此时如果上位机按这种方式去解析的时候,那么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控制器来说,也存在这个大端数据小端读、小端数据大端读的现象。
综上,PMAS与Modbus 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库,本文不再赘述该库的使用!
总结
- Modbus TCP通信中,主站为client端,从站为server端;
- 在PMAS中可简单认为Modbus寄存器是事先定义好的数组,Modbus数据的传输通过读写这些数组实现;
- PMAS端的CDAB对应Modbus Poll端的ABCD,PMAS端的ABCD对应Modbus Poll端的CDAB;
- PMAS中寄存器索引从0开始,Modbus Poll端从1开始,其他设备也应该确认是否存在此差异!
以上内容为笔者在工程实践过程中的个人总结,如有错误还请各位看官批评指教!
读万卷书也要行万里路,我是罗伯特祥,下次见~
参考文献:
评论(0)
您还未登录,请登录后发表或查看评论