PLC通信协议的转化
在自动化程序设计中,常常需要对通信协议进行相互转化。例如,某个控制器需要通过PLC控制设备的某个部件的运动,但PLC只支持ModbusTCP协议,而控制器只支持CanOpen通讯协议。这时,就需要一个网关进行通信协议的转化。网关有可编程网关和不可编程网关之分,其中后者只能支持简单的转化,如将一句Modbus的报文完整转换为CanOpen,但无法实现将Modbus报文的第一字节转换为CanOpen的第三个PDO的第四个字节等这样的复杂逻辑。所以,如需要对不同的通信协议的报文内容进行任意的拼接组合,则需要使用可编程网关。
本文介绍一下使用有Codesys运行环境的可编程网关进行ModbusTCP和CanOpen通信协议相互转化的程序。这里以广成科技的GCAN-410产品的Codesys版本为例[3],说明如何令该可编程网关同时做CanOpen的从站和ModbusTCP的主站,从而让做CanOpen主站的控制器和做ModbusTCP从站的PLC通过该网关进行通信。其它的协议互转的思路也类似。
一、Codesys简介
首先,Codesys是一种国际通用的工业自动化领域的编程系统,支持IEC 61131-3标准的PLC编程语言[1]。这里主要使用ST语言进行可编程网关的通信协议转换程序设计。例如,可编程网关可以通过Modbus得到PLC的一些变量的值,然后将这些值嵌套在通过CanOpen发出的PDO报文中的规定位置,使得CanOpen主站的控制器可以得到PLC里的数据信息;当然,可编程网关也可通过CanOpen接收到控制器发出的报文,从收到的PDO中提取出数值,并将数值通过Modbus传递给PLC。
二、Modbus简介
Modbus是一种工业通信协议的标准,是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型[2]。这里使用ModbusTCP。简单地说,在Modbus通信中,通信设备有主站也有从站,主站主动发送消息,从站被动回复消息。Modbus的消息分为读取离散输入,读/写线圈,读取输入寄存器,读/写保持寄存器这几种。当主站发出"读取地址从A到B的离散输入"后,从站会将地址在A和B之间的离散输入回复给主站;输入寄存器同理,区别在于离散输入是布尔量,输入寄存器是16位的数值变量;而主站发出"写地址A的线圈为某值"后,从站会把自己的内部的Modbus地址为A的变量写成该值,并返回"写成功"的消息;保存寄存器同理。
Modbus从站中可以设置,或已经内置了每个Modbus地址和PLC内部变量地址的对应关系,如数字输入1对应Modbus的离散输入的地址1,数字输出1对应Modbus的线圈地址1,内部只读变量x对应Modbus的输入寄存器地址10000,内部可读可写变量y对应Modbus的保持寄存器地址20000。所以,Modbus主站通过Modbus报文,可以读取PLC的变量,也可以对PLC的变量进行修改。
三、CanOpen简介
CanOpen是一种基于CAN,在工业自动化中广泛应用的通信协议[4]。一般来说,CanOpen的主站和从站都会发送4个PDO报文,每个PDO报文长度不超过8字节。设备刚启动时,主站会通过NMT报文对从站进行初始化,确保通信能正常进行。控制器,或PLC,都可以设置或已经内置PDO报文里的每一个字节对应的系统内部变量。发出的PDO值里的各字节是PLC内置的各变量的值,而当收到PDO时,里面的字节内容会被用于更新PLC内置的变量值。
四、可编程网关的程序设计
广成科技的GCAN-410产品有一些内置的函数,可以把要通过Modbus和CanOpen发送的报文的数据保存在数组型变量里,同时又有数组型变量存放收到的报文中的数据。
在这里通过调用一些函数,得到这些报文携带的数据。
CanOpenSend(arTPDO_Data := DataCanTransmit, arRPDO_Data => DataCanReceive);
ModbusVariable(arReadBufDiscreteInput => DataModbusReceiveDiscrete, arReadBufInputRegister => DataModbusReceiveAnalog,arWriteBufCoil := DataModbusTransmitDiscrete, arWriteBufHolding := DataModbusTransmitAnalog);
在以上代码中,CanOpenSend是将该网关需要发送和接收的CanOpen报文数据与网关内部的变量绑定在一起的函数。该函数的实现方式随PLC品牌的不同而不同,此处不赘述。该可编程网关要发送的CanOpen的PDO保存在变量DataCanTransmit中,而接受到的PDO保存在变量DataCanReceive中。这两个变量数组均为2维数组,即包含4个8字节数组的大数组。所以,DataCanReceive[1][4]代表接收到的PDO2的第5字节(ST语言中,数组下标从0开始)。发送同理。函数ModbusVariable也类似,但只是1维数组,且在网关中已经设置了Modbus读写的地址范围。
(一)字节变量的转换
例如,我要将网关通过CanOpen收到的PDO2的第8字节对应到Modbus从站的PLC的地址为10的保持寄存器对应的变量,又假定我在ModbusVariable函数中已经设定了网关对Modbus保持寄存器的写动作作用地址为10,则代码如下
DataModbusTransmitAnalog[0] := DataCanReceive[1][7];
DataModbusTransmitAnalog[0]即为写给保持寄存器的地址10的第1字节的数据(注意Modbus的寄存器的一个地址对应2个字节)。
(二)二进制比特的提取和转换
另外,如果在CanOpen收到的PDO3的第3字节是控制器的8个数字输入(一个数字输入占据一个二进制比特),而我希望把第1和第2个数字输入传到PLC的Modbus的第1和第7线圈,如何实现?
DataModbusTransmitDiscrete[0] := DataCanReceive[2][2] AND 16#1;
DataModbusTransmitDiscrete[6] := DataCanReceive[2][2] AND 16#2;
上述代码实现了该功能。DataCanReceive[2][2]是收到的整个字节,而通过将其和16#1及16#2(16#在PLC的ST语言中代表16进制,所以它们用二进制表示则为00000001和00000010)进行AND运算,故只有第1位会进入DataModbusTransmitDiscrete[0]即第1线圈,第2位会进入DataModbusTransmitDiscrete[6]即第7线圈,其它位均被屏蔽。
如果是反过来,要通过读取多个Modbus的离散输入,并将这些比特输入并排形成一个字节传入CanOpen,如何实现?
DataCanTransmit[0][2] := DataModbusReceiveDiscrete[6] AND 16#1;
DataCanTransmit[0][2] := DataCanTransmit[0][2] + (DataModbusReceiveDiscrete[7] AND 16#1) * 2;
在这里,DataModbusReceiveDiscrete[6]是Modbus PLC的地址为6的离散输入,DataModbusReceiveDiscrete[7]是Modbus PLC的地址为7的离散输入,该代码将前者用作PDO1的第3字节的第1比特,后者用作PDO1的第3字节的第2比特。
第一行,将第1比特赋予了地址为6的离散输入的值。第二行,对于地址为7的离散输入的值,在DataModbusReceiveDiscrete[7]中同样处于第1比特。所以要让它移位到第2比特,即提升1个比特,需要用数学运算进行,这里的运算即为* 2。对一个数值乘以,相当于把该数值的所有2进制位提升了位[5];当然,整除也相当于把该数值的所有二进制位下降了位。
五、小结
有Codesys运行环境的可编程网关,例如广成科技的GCAN-410,可以通过编程实现各种通讯协议之间的互转,且报文可以互相拼接。拼接的方式,就是将报文的内容对应的变量拆解开,然后将数值赋予另一个通讯协议的变量。对于二进制比特,用AND逻辑进行屏蔽,以及用乘除法进行移位。
参考资料
[1]玩转CODESYS 入门篇(一)-- 认识CODESYS-CSDN博客
[2]ModbusTCP协议 - ioufev - 博客园
[3]沈阳广成科技有限公司
[4]CANopen Tutorial - Siemens
[5]计算机中二进制的移位运算_二进制的乘法移位原理-CSDN博客