11 附录:PNDriver

11.1 简介

在之前的文章中,我们聊过了EtherCAT主站和ProfiNET从站。EtherCAT从站需要专用ASIC如LAN9253、AX58200、XMC4300等实现,底层还是倍福的SSC。而ProfiNET主站一直都没有开源实现。

西门子官方其实是有ProfiNET主站和从站代码的,叫做PNDriver,与之配套的还有PNConfigLib。目前最新版本为 PNDriver3.2、PNConfigLib1.5

PNDriver一直在更新迭代,但其主要为CP1625客户提供,需要密码才可以解压。它既可以作为主站,也可以作为从站使用。源代码可以跑在CP1625、Windows和Linux上。

11.2 解密

在PNDriver的早期版本中,源代码被封装为了ISO文件再经过加密,而最近的版本直接通过ZIP压缩并加密,这导致了一个漏洞可以被利用,基本原理是通过 bkcrack 检测ZIP中仅压缩且未混淆的文件,通过明文去恢复内部密钥,再通过密钥对ZIP文件解密。

要进行解密,我们需要先在压缩包内找到算法为ZipCrypto Store的文件,例如PNDriver_03.02\contrib\Debian_Packages\pndevdrv-dkms_03.02.00.00.00.01.00.15_amd64.deb。然后,我们需要知道这个文件的一段明文,deb文件通常以以下字符开头,注意换行符为LF:

!<arch>
debian-binary

之后就可以照着bkcrack的介绍进行密钥恢复了,恢复出来的是内部密钥,只可以将zip压缩包的密码解除,要想获取原始密码还是有一定难度,不在本文讨论范围。拿到源代码后,我们就可以开始测试了。本文之后主要讨论Linux(LinuxCNC)下的ProfiNET主站应用,带一个V90从站跑RT(非实时)。

11.3 配置环境和编译

PNDriver的基础文档做的很漂亮,我们主要看Quick Start for Linux Native这部分。我习惯使用VSCode,安装和配置照着文档做就可以。

Linux默认的端口范围不满足,可通过sudo sh -c 'echo "49152 65535" > /proc/sys/net/ipv4/ip_local_port_range'临时修改。

建议首次编译pn_driver\src\examples\ioc\test_app,ioc表示controller(主站)。test_app中已有部分基础代码,运行之后,通过2、5、9、10来依次选择网卡、启动主站、连接从站。主站可以启动就没问题了,默认的从站是ET200SP我们没有,需要换成V90。

11.4 使用PNConfigLib

之前我提到过西门子的一大特色是稳定,另一大特色是臃肿。PNConfigLib就是一个典型。由于GSDML设备描述文件无法直接被PNDriver使用,当存在多个从站时,需要对从站分配IP、检查拓扑结构,这部分的描述文件转换过程被做到了PNConfigLib中,即通过PNConfigLib生成中间层xml文件,再由PNDriver读取并驱动从站。

PNConfigLib的技术栈用的是VC++混合C#和.net 8.0,所以推荐在windows系统下操作。我们需要参考PNConfigLib_01.05\buildtool\README.md文档,对源代码进行编译,需要vs2022和.net开发环境。编译之后,可执行文件在PNConfigLib_01.05\bin\Release\x64\PNConfigLibRunner.exe

编译出来的文件不带界面,还是需要通过命令行操作,操作的文档在PNConfigLib_01.05\examples\Readme.md。对于我们测试来说,我们只用basic configuration,即如下指令:

.\PNConfigLibRunner -c "..\..\..\examples\01_Basic_Configuration\Configuration.xml" -l "..\..\..\examples\01_Basic_Configuration\ListOfNodes.xml"

执行后,将自动新建文件夹PNConfigLib Output - [日期时间],里面的PROFINET Driver_PN_Driver_1.xml就是PNDriver需要的中间描述层。

本文的目标是带V90伺服,所以我们还需要将V90的GSDML拷到examples\GSDMLs下,并修改01_Basic_Configuration下的两个xml。其中,Configuration.xml存放站名、插槽交互,主体内容如下:

<Configuration schemaVersion="1.0"
               ConfigurationID="ConfigurationID"
               ConfigurationName="ConfigurationName"
               ListOfNodesRefID="ListOfNodesID"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns="http://www.siemens.com/Automation/PNConfigLib/Configuration"
               xsi:schemaLocation="http://www.siemens.com/Automation/PNConfigLib/Configuration Configuration.xsd">
    <Devices>
        <CentralDevice DeviceRefID="PN_Driver_1">
            <CentralDeviceInterface InterfaceRefID="PN_Driver_1_Interface">
                <EthernetAddresses>
                    <IPProtocol>
                        <SetInTheProject IPAddress="100.168.0.1"
                                         SubnetMask="255.255.255.0"
                                         RouterAddress="100.168.0.100" />
                        <!--<SetDirectlyAtTheDevice/>-->
                    </IPProtocol>
                    <PROFINETDeviceName>
                        <PNDeviceName>pndriver1</PNDeviceName>
                    </PROFINETDeviceName>
                </EthernetAddresses>
                <AdvancedOptions>
                    <RealTimeSettings>
                        <IOCommunication SendClock="1" />
                    </RealTimeSettings>
                </AdvancedOptions>
            </CentralDeviceInterface>
        </CentralDevice>
        <DecentralDevice DeviceRefID="V90_1">
            <DecentralDeviceInterface InterfaceRefID="V90_1_Interface">
                <EthernetAddresses>
                    <IPProtocol>
                        <SetInTheProject IPAddress="100.168.0.2"
                                         SubnetMask="255.255.255.0"/>
                    </IPProtocol>
                    <PROFINETDeviceName DeviceNumber="1">
                        <PNDeviceName>sinamics-v90-pn</PNDeviceName>
                    </PROFINETDeviceName>
                </EthernetAddresses>
            </DecentralDeviceInterface>
            <!-- GSDML reference of the plugged module: -->
            <Module ModuleID="DRIVE_OBJECT"
                    SlotNumber="1"
                    GSDRefID="IDM_DRIVE">
                <Submodule SubmoduleID="Module_Access_Point"
                           SubslotNumber="1"
                           GSDRefID="IDS_MAP">
                </Submodule>
                <Submodule SubmoduleID="without_PROFIsafe"
                           SubslotNumber="2"
                           GSDRefID="IDS_NOSAFE">
                </Submodule>
                <Submodule SubmoduleID="SIEMENS_telegram_111_PZD_12_12"
                           SubslotNumber="3"
                           GSDRefID="IDS_TEL111">
                    <IOAddresses>
                        <InputAddresses StartAddress="100" />
                        <OutputAddresses StartAddress="100" />
                    </IOAddresses> 
                </Submodule>
                <Submodule SubmoduleID="Supplementary_telegram_750_PZD_3_1"
                           SubslotNumber="4"
                           GSDRefID="IDS_TEL750">
                    <IOAddresses>
                        <InputAddresses StartAddress="200" />
                        <OutputAddresses StartAddress="200" />
                    </IOAddresses> 
                </Submodule>
            </Module>
        </DecentralDevice>
    </Devices>
</Configuration>

这个文件的插槽GSDRefID需要去GSDML里面查,排序可以参考codesys或博图,GSDML里也有允许顺序。之后,还需要修改ListOfNodes.xml,如下:

<ListOfNodes schemaVersion="1.0"
             ListOfNodesID="ListOfNodesID"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://www.siemens.com/Automation/PNConfigLib/ListOfNodes"
             xsi:schemaLocation="http://www.siemens.com/Automation/PNConfigLib/ListOfNodes ListOfNodes.xsd">
    <PNDriver DeviceID="PN_Driver_1"
              DeviceVersion="v3.1"
              DeviceName="PROFINET Driver">
        <Interface InterfaceID="PN_Driver_1_Interface"
                   InterfaceType="Linux Native"
                   InterfaceName="PN_Driver_1_Interface" />
    </PNDriver>
    <DecentralDevice DeviceID="V90_1"
                     GSDPath="../GSDMLs/GSDML-V2.32-Siemens-Sinamics_V90-20190415.xml"
                     GSDRefID="IDD_V90PN-V4.7"
                     DeviceName="V90_1">
        <Interface InterfaceID="V90_1_Interface"
                   InterfaceName="V90_1_Interface" />
    </DecentralDevice>
</ListOfNodes>

这样,再通过PNConfigLibRunner生成出来PROFINET Driver_PN_Driver_1.xml,我们就可以拿到PNDriver中使用了。

11.5 PNDriver的修改

我们测试只要跑通总线即可,这样只需要将中间XML拷到examples/ioc/test_app/linux_native/PNDriverBase_TestApp/,再修改pnd_test.c的init_paths部分即可。

需要注意的是vscode需要以管理员权限运行,通过sudo code --no-sandbox --user-data-dir="./temp"启动vscode,安装C++、CMAKE等插件就可以编译。

PNDriver不太容易看出来总线的状态和交互,可以结合WireShark抓包调试。通讯成功后,就可以把TestApp按需求重写。

11.6 结尾

少量的ProfiNET主站需求推荐用CODESYS/TwinCAT来做,有界面和诊断,调试起来很简单。PNDriver则适合于批量产品或特定软件研发,它还支持IRT同步(仅CP1625)和作为从站使用,具体参考示例代码。