3 各类总线的专用设置

3.1 总线设备的添加与版本更新

在codesys中,Device下添加的设备为总线。您也可以在 官方help 上查到关于各设备的具体信息。

设备中有一些隐藏的参数,如EtherCAT的DCSyncInWindow参数。
在工具-选项,设备编辑器中勾选 Show generic device configuration views 以使能额外的设备配置选项卡:

通讯自动优化

默认情况下不使用的I/O地址不会更新变量实时值,可在I/O Mapping页右下角的Always update variables改为Enable 2即可。

3.2 EtherCAT的专用设置

系统设置

- Linux SL/Raspberry pi:不需要特殊的设置。若有DC同步报警,可修改DCInSyncWindow参数,建议改大到500us。
- ControlWin:需要安装 WinPcap 软件才可以运行EtherCAT,且其不具有实时性,不可以运行SoftMotion。
- ControlRTE:需要确保网卡已经安装了专用驱动,您应该可以在设备管理器的网络适配器中看到网卡为CoDeSys Gigabit Network。若开DC和不开DC时发包数量不一致,则需要考虑重装RTE。

EtherCAT总线设备分为两个,EtherCAT Master和EtherCAT Master SoftMotion,它们除了FrameAtTaskStart参数外完全一致,不带SoftMotion的为FALSE,每个PLC周期结束后才发EtherCAT帧。为TRUE时在每个周期开始时发EtherCAT帧,开启后发送和接收的数据会有延迟,可能会导致超采样出问题,但能提供优异的实时性。

EtherCAT总线依赖设备描述文件,您需要先在工具-设备管理器中安装设备,安装后的设备即可插入在EtherCAT Master下。EtherCAT还有设备树的规范,若您插入的设备是耦合器(如倍福EK1100),则EK1100下挂的模块都需要在EK1100设备上添加。之后的设备在EtherCAT Master下添加。

当您创建EtherCAT Master设备时,会自动创建EtherCAT_Task任务。该任务的周期由EtherCAT Master设备的Cycle Time参数指定,请勿手动修改。此外,需要选择EtherCAT网卡MAC地址或根据网卡名称自动选择(推荐后者),并建议勾选自动重启从站。

使用运动控制时的任务设置

使用运动控制(SoftMotion)时,运动控制相关程序实例(如MC_Power、MC_MoveAbsolute等)所在的程序块必须安排到EtherCAT_Task下执行。

EtherCAT也支持扫描,添加EtherCAT Master并选择网卡后,登陆一次设备并下载PLC。无需启动,此时在EtherCAT Master设备上右键-扫描设备 即可进行扫描。添加完EtherCAT模块后,可在对应模块的I/O Mapping页中关联变量。

设备关联与地址关联

在使用TIA时我们通常使用地址关联,直接将变量关联到I、Q地址上。但在CODESYS中,我推荐在I/O Mapping中关联变量,这种方式不受设备地址影响,不容易出错。TC3需要定义AT %I*/Q*才可以映射,而CODESYS不需要这样做,可以关联任意变量。当然,大量的连续数组仍然推荐直接映射地址。

添加设备后,有部分设备会需要修改Startup Parameters,例如汇川GL20系列的模拟量模块通过设置transform mode改变电压/电流模式。有部分设备可选Process Data,如伺服驱动器配置默认在CSP模式,若要使用PP、PV模式则需要修改Process Data中的选项。可按实际情况修改。

3.3 Profinet的专用设置

根据经验来说,CODESYS的Profinet仅使用RT模式,通讯周期在2到32MS。IRT需要特殊硬件才可以实现。由于EPOS的功能块只在博图软件里闭源,所以PTP带轴的实现也极少。大多数情况下,CODESYS的PN需求都是旧设备改造、总线测试或协议转接。

作为PN主站时,操作和EtherCAT非常相似,添加PN主站、扫描或手动添加设备,登录即可。如需分配地址可在扫描时设定。PN使用普通以太网交换机就可实现星形连接,每个设备都有独立的、在一个网段下的IP。与EtherCAT不同的是,一般来说PN设备下插入的为报文Telegram(或叫模块Module),仍然是在这个设备上。

无论作为PN主站还是PN从站,都是在Ethernet设备下加PN主/从,而不是用CIFX卡。当PLC作为PN从站时,Windows系统中需要将防火墙打开,Linux系统中需要编辑配置文件,在最下面增加:

[SysEthernet]
QDISC_BYPASS=1
Linux.ProtocolFilter=3

[SysSocket]
Adapter.0.Name="eth0"
Adapter.0.EnableSetIpAndMask=1
Adapter.1.Name="eth1"
Adapter.1.EnableSetIpAndMask=1

当作为PN从站时,GSDXML设备描述文件可以从设备管理器的Fieldbuses - Profinet IO - Profinet IO Device下导出,注意有3.5.17和4.2.0两个版本可能混用,请确认实际使用的设备版本。此外,请确认CODESYS项目中的设备树和TIA项目中的设备树使用同样的Module(例如都用InOut_64_Byte)。

不建议使用EnableSetIpAndMask

PLC作为PN从站时,建议固定地址而不是使用 EnableSetIpAndMask ,即站点名称和IP都在CODESYS项目中指定。如果您需要可变的IP,请确保由上位PLC指定的IP不会与其它网口在同一网段下,并且 避免用PN从站所使用的网口登录到PLC

3.4 Modbus RTU的专用设置

在Linux系统中,需编辑配置文件指定COM端口分配。例如:

[SysCom]
Linux.Devicefile.1=/dev/ttyAMA0
Linux.Devicefile.2=/dev/ttyUSB0

也可以直接归纳到ttyUSB中自动排序,例如:Linux.Devicefile=/dev/ttyUSB,这会自动把ttyUSB0映射到COM1,以此类推。

在Windows系统中,请在设备管理器中查看具体的COM端口。

Modbus没有设备描述文件,使用统一的Modbus Slave设备,根据对应的文档编辑Modbus Slave Channel来指定通讯内容和地址。通常使用的功能码为3和16,请注意通讯地址是10进制还是16进制。

3.5 Modbus TCP的专用设置

ModbusTCPSlave Parameters中有个参数Unit-ID,可以理解为RTU的从站ID。标准协议中已移除此校验,默认置为0xFF。但某些旧设备仍然保留该校验,可手动改为0x01或设备特定的ID。

除了标准的Modbus TCP slave设备外,还可以在其下添加Modbus Slave, COM Port,即TCP转RTU方案,可以用协议转换器或某些带RTU扩展的TCP模块。但部分带协议转换的转换器无需设置,相当于直接把TCP报文转为了RTU报文,这时Unit-ID是需要根据实际设备地址修改的。

3.6 Ethernet/IP的专用设置

EIP需要注意连接路径,有些设备连接路径不一定是设备描述文件中的。连接路径无法扫描,必须手动输入,不能有任何错误。

EIP也可以不使用设备描述文件,而是通过标准设备(Generic EtherNet/IP device)添加自定义连接路径来连接。大多数情况下,非标通讯只有OT和TO,只修改Instance ID和IO大小即可,其余选项不需要配置。

3.7 Canopen的专用设置

通常情况下,Linux上的Canopen会通过SocketCAN,使用CANable或MCP2515实现。而Windows上一般使用PCAN USB转换器实现。这两种都只支持标准CAN协议,最大波特率1M。

在Linux(Raspberry Pi)中,需要做如下修改:

编辑配置文件,在末尾增加如下行(4.5.0.0之后不再需要):

[CmpSocketCanDrv]
ScriptPath=/opt/codesys/scripts/
ScriptName=rts_set_baud.sh

此外,can驱动加载可能晚于运行时导致无法通讯,可以关闭运行时自启动sudo systemctl disable codesyscontrol,之后延迟手动启动codesys服务。编辑/etc/rc.local,在exit 0之前插入:

sleep 5
sudo service codesyscontrol restart

而在windows中,若使用PCAN USB转换器,则需要编辑配置文件,在[ComponentManager]下添加一行Component.X=CmpPCANBasicDrv(将X更改为顺序的编号)

3.8 TCP UDP的专用设置

对于TCP、UDP的底层我们不再重复介绍,在PLC的实际应用中,我们一般把TCP作为可检测、可靠的UDP使用,即把TCP当作收发包而不是流模式使用。为了达成这一点,我们在一个PLC周期内对一个IP、端口只做一次读和/或写操作。如果在一个周期内多次写包,则在接收端可能被自动粘包。单个包的大小超过一定数量也会被拆包,需要在应用层做粘包操作。TCP的粘包操作是个伪概念,但在嵌入式和PLC设备中很好理解,部分设备会用 $r$n 换行符做数据包结尾,标准的Modbus TCP遇到粘包也会舍弃有效数据包后面的数据。

如果有大量数据传输的需求(大于MTU,一般是1500Byte),则在两个TCP帧的处理期间有概率会和PLC任务周期碰撞,导致偶发性的拆包。例如,一个2500Byte的数据,有时候收到完整的2500Byte,有时候被分割成两段1500+1000Byte由两个PLC周期处理。这种情况下需要在应用层做粘包操作。

TCP和UDP都是使用NBS库实现,在CODESYS中分为两个库,一个是官方库(Net Base Services),另一个是CAA库(CAA Net Base Services)。CAA的全称为CoDeSys Automation Alliance,现在已经很少提及,但代码非常稳定。本文档的TCP和UDP以CAA库实现,官方库的实现方法会有细微的区别。

TCP、UDP的通讯实现都是纯代码的,这意味着不需要额外的授权,同样的代码可以在汇川、施耐德等基于CODESYS的PLC上通用。

命名空间

官方库和CAA库的命名空间(Namespace)都是NBS,所以不建议在一个项目中同时使用两个库。如果一定要使用,需要手动修改命名空间。

这里是一个基础的TCP服务器的实现代码:

VAR
    ip:NBS.IP_ADDR:=(sAddr:='0.0.0.0');//作为服务器时不需要设置IP,用0.0.0.0即可
    port:UINT:=55555;//服务器端口
    Server:NBS.TCP_Server;
    Connection:NBS.TCP_Connection;
    Write:NBS.TCP_Write;
    Read:NBS.TCP_Read;
    ton_LostConnect:TON;
    sReadData:STRING;
    xWriteEnable: BOOL;
    sWriteData:STRING;
END_VAR


//Server
    Server(xEnable:=NOT ton_LostConnect.Q , ipAddr:=ip ,uiPort:=port );
//Connect
    Connection(xEnable:=Server.xBusy AND NOT ton_LostConnect.Q ,hServer:=Server.hServer );
//Lost Connect
    IF NOT Connection.xActive THEN
        IF ton_LostConnect.Q THEN
            ton_LostConnect(IN:=FALSE);
        END_IF
        ton_LostConnect(IN:=TRUE,PT:=T#2000MS);
    END_IF
//Read
    Read(xEnable:=Connection.xActive AND NOT Read.xError ,hConnection:=Connection.hConnection , szSize:=SIZEOF(sReadData) , pData:=ADR(sReadData));
//Flush String
    IF Read.szCount <> 0 THEN
        sReadData:=LEFT(sReadData,ULINT_TO_INT(Read.szCount));
    END_IF
//Write
    Write(xExecute:=xWriteEnable , udiTimeOut:=500 , hConnection:=Connection.hConnection , szSize:=INT_TO_UDINT(LEN(sWriteData)) , pData:=ADR(sWriteData));

这是一个基础的TCP客户端的实现代码:

VAR
    client:NBS.TCP_Client;
    ip:NBS.IP_ADDR:=(sAddr:='192.168.2.100');//这里改为目标服务器的IP地址
    port:UINT:=1000;//改为目标端口
    read:NBS.TCP_Read;
    write:NBS.TCP_Write;
    strRead:STRING(99);
    strWrite:STRING(99);
    xWriteEnable: BOOL;
    tonDelay:TON;
    tCyclicDelay:TIME:=T#10MS;
    xStartCyclic:BOOL:=TRUE;
    ton_LostConnect:TON;
END_VAR


//Client
    client(xEnable:=NOT ton_LostConnect.Q ,ipAddr:=ip ,uiPort:=port);
//Lost Connect
    IF NOT client.xActive OR write.xError THEN
        IF ton_LostConnect.Q THEN
            ton_LostConnect(IN:=FALSE);
        END_IF
        ton_LostConnect(IN:=TRUE,PT:=T#2000MS);
    END_IF
//Read  
    read(xEnable:=client.xActive ,hConnection:=client.hConnection , szSize:=SIZEOF(strRead) , pData:=ADR(strRead));
    IF read.szCount > 0 THEN
        strRead:=LEFT(strRead, ULINT_TO_INT(read.szCount));//only keep actual data
    END_IF
//Write
    write(xExecute:=xWriteEnable , hConnection:=client.hConnection , szSize:=LEN(strWrite) , pData:=ADR(strWrite));
    tonDelay(IN:=xStartCyclic,PT:=tCyclicDelay);
    xWriteEnable:=FALSE;
    IF tonDelay.Q THEN
        tonDelay(IN:=FALSE);
        xWriteEnable:=TRUE;
    END_IF