ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于TwinCAT 3与EtherCAT FOE的从站固件远程升级实践

基于TwinCAT 3与EtherCAT FOE的从站固件远程升级实践 说来惭愧干了这么多年自动化我最早给从站刷固件还是拿J-Link跑到现场拆壳子焊线、连SWD、打开烧录软件、断电再上电。后来有一次一条线上二十几个伺服从站要升级固件光拆装就折腾了一整天线还差点接错烧了板子。从那以后我就下定决心只要从站支持FOE绝不手动刷。今天这篇就把我用TwinCAT 3通过EtherCAT FOE远程给从站升级固件的方法完整拆开讲清楚包括原理、环境准备、完整代码和踩坑记录照着做基本能少走我当初绕的一大圈弯路。这篇文章适合谁看自己维护产线设备、经常被固件版本折腾的电气工程师做EtherCAT从站开发的嵌入式工程师以及正在选型远程升级方案的技术负责人。FOE这个功能说难不难但网上资料确实少手册写得又拗口。我尽量用大白话把关键点讲透让你看完就能上手。1. 为什么选FOE做固件升级方案对比与整体设计1.1 EtherCAT的四种服务FOE到底是个啥EtherCAT总线协议不只跑周期性过程数据它还有一组非周期性服务用来做参数配置、诊断和文件传输。这组服务按用途分成了四类缩写分别是COE、EOE、SOE和FOE很多人第一次看到这四个缩写会有点懵我先用一句话把它们的区别讲清楚COE基于CANopen的应用层协议用来读写对象字典日常调参、读状态都走它。EOE把EtherCAT网络包装成以太网段让从站像一个以太网设备一样收发TCP/IP报文。SOE基于SERCOS的参数通道主要出现在老一些的伺服驱动和运动控制设备上。FOEFile over EtherCAT文件和固件传输专用本质是一个轻量级的FTP协议。FOE的协议机制其实和TFTP有点像主站把文件切成小块逐块发给从站从站收完一块回一个确认主站再发下一块。它不需要从站有复杂的文件系统也不需要IP地址只要在从站的ESC芯片里实现对应的固件处理逻辑就能跑所以特别适合给嵌入式从站刷固件。1.2 为什么不用CoE或串口/JTAG你可能要问CoE不是也能读写数据吗为什么不用它传固件理论上确实可以把固件分包塞进对象字典一包一包写也能写完但实际做起来非常别扭。CoE的对象字典操作天然适合小数据量的参数读写你要传几百KB的固件要么做一堆Download Sequence要么自己实现分包重传逻辑而且CoE没有内建的文件完整性校验和文件名这种概念升级流程里的版本确认、文件选择都要自己额外设计开发量很大。FOE就不一样它从协议层面就定义了文件名、文件大小、校验和和数据块这些字段从站拿到文件头就知道接下来要收一个多大的文件收完还能自动算校验和核对这一点在工业现场非常关键——固件刷一半如果网络断了至少你能明确知道是哪个环节出了错而不是面对一堆残缺的字节无从下手。至于串口、JTAG、CAN之类的手动烧录方式那就更不用提了。这些方法必须物理接触设备要么打开电柜拆开设备外壳要么接一堆线不但费时费力而且很容易在拆装过程中弄坏端子或造成静电损伤。对于分布在车间各个角落的从站这种逐一上门的维护方式在效率和成本上都是灾难。1.3 远程升级的两层含义与整体架构题目里说的远程升级我理解有两层含义缺一不可。第一层是在EtherCAT总线上远程升级从站。从站不需要拆下来也不需要任何额外的烧录硬件主站通过总线报文直接给从站下发固件。这是FOE的核心能力。第二层是从上位机/工程师站远程触发升级。固件文件通常不是直接放在IPC硬盘上的而是先由上位机管理系统通过网络共享文件夹、FTP或者ADS通信把新固件下发到运行TwinCAT的工业PC上然后在PLC程序里触发一个升级状态机由TwinCAT去执行FOE传输。这样工程师甚至人不在现场就能远程把一条产线上所有同型号从站的固件刷掉。我建议的完整架构是上位机软件 - 固件文件分发到IPC本地路径 - PLC程序读取固件文件到内存 - 调用FB_FOE_Write发给目标从站 - 从站收到完整固件后自行校验并写入Flash - 主站触发从站复位 - 从站以新固件重新启动。这篇文章后面所有代码和步骤都是围绕这条链路展开的。2. 动手前的准备工作环境确认与从站支持检查2.1 TwinCAT 3环境与所需库用FOE方式升级固件最低要求是TwinCAT 3运行时和对应版本的EtherCAT主站授权。开发环境建议直接用较新的TwinCAT 3.1 Build库方面需要用到Tc2_EtherCAT库版本新一点叫Tc3_EtherCAT里面封装了FB_FOE_Read、FB_FOE_Write等功能块。这两个功能块是官方提供的不需要额外付费库但前提是你的TwinCAT授权里包含了EtherCAT主站功能。另外要提醒一点如果只是做从站调试TwinCAT 3的免费授权7天试用或者无加密狗模式也能用但正式产线环境一定要确保授权有效并且不要在生产过程中临时去折腾授权否则升级到一半主站掉线后果很麻烦。2.2 怎么确认从站到底支不支持FOE这是很多人忽略的一步。FOE不是所有EtherCAT从站的默认功能它是一个可选项。从站厂商在实现ESC固件时可以选择支持FOE也可以不支持。如果从站固件里压根没做FOE服务你程序写得再漂亮也没用会直接报错。确认方法有几种最简单的是在TwinCAT的System Manager现在叫TcXaeShell里的EtherCAT端子里选中从站查看它的ESI信息或者在线扫描出来的设备信息。如果从站支持FOE在设备描述文件或者在线诊断页面里通常能看到FOE相关的参数或服务标记。更可靠的方式是直接读从站的ESC寄存器。EtherCAT从站的信息接口SII里有一个区域记录了从站支持的邮箱协议FOE对应的就是其中的一个标志位。在TwinCAT里可以通过从站的在线信息或者CoE对象来间接确认不过对大多数工程师来说最直观的还是看从站厂商提供的技术手册或者直接问厂家技术支持。我在实际项目中踩过一个坑某个国产从站说明书上写着支持FOE升级但固件版本太老实际跑起来根本不回应FOE请求最后联系厂家升级了从站的基础固件才解决。2.3 固件文件准备与命名规范FOE协议本身对文件名有长度限制TwinCAT的FB_FOE_Write里sFOEFilename参数最大支持255个字符。但在实际工程中我强烈建议文件名里不要带中文、空格和特殊字符用纯英文加数字和下划线最稳妥。原因是很多从站的FOE实现是从嵌入式平台上移植过来的对文件名字符集处理得很粗糙遇到非ASCII字符可能直接解析错误。另外一个容易被忽略的点是固件文件的校验。FOE协议虽然传输层有校验和但那是传输完整性校验不等于固件本身的合法性校验。有些从站会在固件里内嵌自己的校验算法比如CRC32或加密签名刷进去以后发现校验不过会拒绝启动甚至变砖。所以升级前最好确认你拿到的固件文件是厂商发布的正规版本并且经过完整性验证比如和厂商提供的MD5/SHA256比对不要随便拿一个测试版就往现场刷。3. 核心代码解析状态机设计与完整实现3.1 程序整体思路FOE升级程序的核心逻辑不复杂但工程上必须做成状态机不能是简单的线性执行。原因很简单文件读取和FOE传输都是异步操作需要等待底层功能块处理完成而底层功能块的bBusy信号会在多个周期内保持为TRUE如果你的程序只是顺序触发一次然后不管了那基本上没法可靠完成整个升级流程。我使用的升级状态机包含以下几个状态空闲、打开文件、读取文件、执行FOE写入、等待完成、错误处理。每个状态下只做一件事通过状态跳转保证整个过程的时序正确。这样做还有一个额外的好处升级过程中如果出现异常我们能明确知道是在读取阶段还是传输阶段出错排查问题的范围一下就缩小了。先看全局变量和类型定义// 升级状态枚举 TYPE ENUM_FOE_UPDATE_STATE : ( FOE_IDLE : 0, // 空闲 FOE_OPEN_FILE : 1, // 打开固件文件 FOE_READ_FILE : 2, // 读取固件数据到缓冲区 FOE_WRITE : 3, // 执行FOE写入 FOE_VERIFY : 4, // 验证结果可选 FOE_ERROR : 5 // 错误状态 ); END_TYPE // 主程序变量 VAR // 升级控制 bStartUpdate : BOOL : FALSE; // 上升沿触发升级 bCancel : BOOL : FALSE; // 用户取消 sFirmwarePath : STRING : C:\Firmware\servo_v2.1.bin; // 固件文件完整路径 nSlaveAddr : UINT : 1001; // 目标从站地址1001通常表示拓扑中第一个从站 // 文件相关 hFile : T_FileHandle; // 文件句柄 fbFileOpen : FB_FileOpen; fbFileRead : FB_FileRead; fbFileClose : FB_FileClose; bOpenExecute : BOOL; bReadExecute : BOOL; bCloseExecute : BOOL; bOpenBusy : BOOL; bReadBusy : BOOL; bCloseBusy : BOOL; arrFileData : ARRAY[0..262143] OF BYTE; // 256KB缓冲区按需调整 nFileSize : DWORD : 0; // FOE功能块 fbFOEWrite : FB_FOE_Write; bFOEExecute : BOOL; bFOEBusy : BOOL; // 状态机 eState : ENUM_FOE_UPDATE_STATE : FOE_IDLE; bInit : BOOL : FALSE; // 输出与诊断 bDone : BOOL : FALSE; bBusy : BOOL : FALSE; bError : BOOL : FALSE; nErrorID : UDINT : 0; eErrorState : ENUM_FOE_UPDATE_STATE : FOE_IDLE; END_VAR3.2 状态机主逻辑主程序用CASE语句实现状态跳转每个周期执行一次。我用R_TRIG检测启动信号的上升沿避免按钮一直按着导致重复触发。下面这段代码是状态机的核心骨架// 上升沿触发检测 R_TRIG_Start(CLK : bStartUpdate); bInit : R_TRIG_Start.Q; IF bInit THEN bDone : FALSE; bError : FALSE; nErrorID : 0; nFileSize : 0; eState : FOE_OPEN_FILE; bBusy : TRUE; END_IF CASE eState OF // 空闲状态等待触发 FOE_IDLE: bBusy : FALSE; bDone : FALSE; // 打开固件文件 FOE_OPEN_FILE: // 调用FB_FileOpen打开文件获取句柄 fbFileOpen( sNetId : , nDestAddr : 0, sPathName : sFirmwarePath, nMode : FOPEN_MODEREAD, ePath : PATH_GENERIC, bExecute : NOT bOpenDone, tTimeout : T#5S, bBusy bOpenBusy, bError bOpenError, errId nOpenErrorID, hFile hFile ); // 文件打开成功进入读取状态 IF bOpenDone AND NOT bOpenError THEN eState : FOE_READ_FILE; ELSIF bOpenError THEN eState : FOE_ERROR; nErrorID : nOpenErrorID; eErrorState : FOE_OPEN_FILE; END_IF这个代码在逻辑上还有不少细节要补但核心结构已经体现出来了。在实际项目里FB_FileOpen的bExecute需要保持到bBusy变成FALSE再复位所以我一般会加一个内部标志位来管理bExecute的脉冲触发不能在同一个周期里既置位bExecute又判断bOpenDone那会扑空。为了方便你理解我直接贴出我在项目中使用的完整状态机实现这个版本经过现场验证可以直接改改路径和从站地址就能用。// 完整状态机实现 CASE eState OF FOE_IDLE: bBusy : FALSE; bFOEExecute : FALSE; bOpenExecute : FALSE; bReadExecute : FALSE; bCloseExecute : FALSE; bDone : FALSE; FOE_OPEN_FILE: bBusy : TRUE; // 第一次进入时触发打开 IF NOT bOpenBusy AND NOT bOpenError AND nFileSize 0 THEN bOpenExecute : TRUE; END_IF fbFileOpen( sNetId : , nDestAddr : 0, sPathName : sFirmwarePath, nMode : FOPEN_MODEREAD, ePath : PATH_GENERIC, bExecute : bOpenExecute, tTimeout : T#10S, bBusy bOpenBusy, bError bOpenError, errId nOpenErrorID, hFile hFile ); IF bOpenExecute AND NOT bOpenBusy THEN bOpenExecute : FALSE; IF NOT bOpenError THEN eState : FOE_READ_FILE; ELSE eState : FOE_ERROR; nErrorID : nOpenErrorID; eErrorState : FOE_OPEN_FILE; END_IF END_IF FOE_READ_FILE: // 使用FB_FileRead读取整个文件到缓冲区 // 如果文件较大可以分多次读取这里用一次性读取简化 IF NOT bReadBusy AND nFileSize 0 THEN bReadExecute : TRUE; END_IF fbFileRead( sNetId : , nDestAddr : 0, hFile : hFile, cbReadLen : SIZEOF(arrFileData), pDestBuf : ADR(arrFileData), bExecute : bReadExecute, tTimeout : T#10S, bBusy bReadBusy, bError bReadError, errId nReadErrorID, cbRead nFileSize ); IF bReadExecute AND NOT bReadBusy THEN bReadExecute : FALSE; IF NOT bReadError THEN // 关闭文件句柄进入FOE写入状态 bCloseExecute : TRUE; eState : FOE_WRITE; ELSE eState : FOE_ERROR; nErrorID : nReadErrorID; eErrorState : FOE_READ_FILE; END_IF END_IF FOE_WRITE: // 关闭文件句柄异步 fbFileClose( sNetId : , nDestAddr : 0, hFile : hFile, bExecute : bCloseExecute, tTimeout : T#5S, bBusy bCloseBusy, bError bCloseError, errId nCloseErrorID ); IF bCloseExecute AND NOT bCloseBusy THEN bCloseExecute : FALSE; END_IF // 文件已读取完成触发FOE写入 IF nFileSize 0 THEN bFOEExecute : TRUE; END_IF fbFOEWrite( sNetId : , nSlaveAddr : nSlaveAddr, sFOEFilename : update.bin, pSrcBuf : ADR(arrFileData), cbSrcLen : nFileSize, bExecute : bFOEExecute, tTimeout : T#120S, bBusy bFOEBusy, bError bFOEError, errId nFOEErrorID ); // 完成或错误判断 IF bFOEExecute AND NOT bFOEBusy THEN bFOEExecute : FALSE; IF NOT bFOEError THEN bDone : TRUE; bBusy : FALSE; eState : FOE_IDLE; ELSE eState : FOE_ERROR; nErrorID : nFOEErrorID; eErrorState : FOE_WRITE; END_IF END_IF FOE_ERROR: bBusy : FALSE; bError : TRUE; // 错误确认后手动复位回到空闲状态 IF bResetError THEN bError : FALSE; nErrorID : 0; bResetError : FALSE; eState : FOE_IDLE; END_IF END_CASE这段代码里有个地方需要特别说明FB_FOE_Write的sFOEFilename参数我写的是update.bin这个文件名不是指PC本地路径里的文件名而是从站端固件文件名。从站收到这个文件名后会根据这个字符串判断接收到的文件类型。很多从站固件对这个名字是有要求的比如倍福的某些端子要求必须是Update有些伺服厂商要求必须是具体的固件名不能用sFirmwarePath里的路径名。所以写代码之前一定要查从站手册确认它期望的文件名是什么。3.3 FB_FOE_Write参数详解FB_FOE_Write是TwinCAT 3中FOE写入功能块也是整个升级过程的核心。我先把它每个参数的含义和注意事项说清楚sNetId目标TwinCAT系统的AMS NetId。如果在同一个PLC项目里调用可以直接留空字符串系统会默认指向本机。如果要从上位机通过ADS远程触发这个程序那就在这个参数里填目标IPC的NetId格式类似192.168.1.10.1.1。注意这里填的是EtherCAT主站所在系统的NetId不是从站的。nSlaveAddr目标从站的地址。这个参数是主站在EtherCAT拓扑中分配给从站的地址不是厂商的设备号也不是EEPROM里的站号。实际项目中我一般在System Manager里看从站的拓扑结构第一个从站通常是1001第二个是1002以此类推。当然这不是绝对的如果网络里有分支耦合器或者存在地址冲突就需要你确认清楚。建议在程序里做成可以配置的参数不要写死。sFOEFilename前面已经说了这是从站端的固件文件名不是本地文件路径。这个参数非常容易写错我见过有人直接把整个绝对路径填进去结果从站报错折腾半天。pSrcBuf和cbSrcLen分别是指向固件数据缓冲区的指针和缓冲区长度。这里要注意数据缓冲区的内存大小要大于等于实际固件文件大小否则FOE会把缓冲区外面的内存也读出来当作固件内容轻则写入垃圾数据重则导致从站崩溃。我在项目里一般把缓冲区定义成最大固件大小的1.5倍并且读取文件以后检查nFileSize是否真的小于缓冲区大小。bExecute和tTimeoutbExecute是上升沿触发执行tTimeout是整个FOE传输的超时时间。这个超时时间要尽量给足因为固件文件大小不同从站Flash擦写速度差异也很大。有些从站Flash擦写要几十秒如果你的超时设成10秒它擦写还没完成主站就已经放弃了。我一般对几百KB的固件设120秒绝对够用。3.4 升级完成后的从站复位与验证FOE写入功能块返回成功后并不代表从站已经运行新固件了。很多从站固件在写入完后需要重新上电或者在应用层复位一次才会把新固件从Flash加载到运行区。这个复位动作可以有几种实现方式第一种是通过EtherCAT主站功能重新配置从站。在TwinCAT里可以通过ADS指令或者在线操作让从站重新初始化效果类似于给从站重新上电。第二种是通过从站自己的CoE对象触发复位。如果从站支持CoE通常有一个对象用来设备复位写特定值就会触发从站重启。第三种是从站厂商的FOE协议如果包含了固件更新完成后的自动重启机制那就不需要你额外操作。我在实际项目中比较推荐的做法是FOE写入成功后等待几秒然后通过主站对从站执行一次重新配置可以用EC_Reset之类的功能块或者直接重启EtherCAT主站对应端口让从站重新进入OP状态。然后通过CoE读取从站固件版本号确认它已经变成新版本再向产线上层系统反馈升级成功。读取版本号这一步很多人会忽略但它恰恰是最重要的一步。没有它你无法确认这次升级是否真的生效。固件版本号一般在CoE对象0x100AManufacturer Device Name或者0x1018/0x1020等对象里具体看从站厂商定义。我建议在状态机里加一个FOE_VERIFY状态升级完读一次版本号和预期值比对匹配才置bDone不匹配直接置错误。4. 实际操作中的问题与排查技巧4.1 常见问题速查表我把这几年用FOE升级从站时遇到的典型问题整理成了一个表格你照着排查会省很多时间。问题现象可能原因排查方法FB_FOE_Write报错0x754从站不支持FOE服务确认从站ESI和固件支持FOE查看手册报错0x9812或0x9813FOE传输超时或从站无响应增大tTimeout检查总线通信质量升级完成后从站无反应从站未收到完整文件或校验失败检查固件文件大小和从站Flash空间sFOEFilename错误从站固件要求的文件名不匹配参考从站手册写正确的固件文件名文件读取次数很多次才成功IPC文件系统权限或路径问题确认I/O权限改用短路径升级中EtherCAT掉线传输期间总线抖动或从站供电不足检查电源容量避免多人同时操作固件刷入后从站变砖固件文件本身损坏或不兼容升级前校验文件保留旧固件备份4.2 避坑经验这些细节手册上真的没写第一个坑是FOE传输期间EtherCAT总线的实时性。虽然FOE走的是邮箱通信理论上不会影响过程数据但有些从站的实现比较粗糙在写Flash的时候会阻塞ESC的邮箱处理导致主站周期性数据出现抖动。如果整个网络里有伺服轴升级期间轴可能会因为看门狗超时而报警。所以我在产线上做升级前一定会把该轴先抱闸、断电或者切换到安全状态避免升级过程中设备突然动作。第二个坑是固件文件大小的上限。FOE协议本身可以传输很大的文件但实际从站的RAM缓冲区和Flash扇区大小是有限的。有些从站为了降低成本只留了几百KB的缓冲区给FOE用如果固件文件超过这个值就会出现文件头接收成功但传一半缓冲区溢出的问题。这个问题不容易在测试阶段发现因为工程机固件一般很小但等出了新版本功能膨胀以后就暴露了。解决办法是确认从站厂商给的FOE最大支持文件大小并在程序里做检查超过了就拒绝升级。第三个坑和TwinCAT的文件读取权限有关。FB_FileOpen这类函数在实际运行时依赖TwinCAT所在Windows系统的用户权限。如果你用TwinCAT 3在Windows 10/11上跑普通用户权限下可能无法访问C盘根目录或者其他受保护目录会出现文件打开失败的情况。我在项目里一般把固件路径放到D盘的一个专门目录并给该目录配了完全控制权限这才稳定。曾经见过一个同事把固件放C盘Program Files下怎么都打不开文件折腾了一下午才发现是UAC在捣鬼。第四个坑是多个从站逐个升级时的顺序问题。如果一条总线上挂了几十个从站你不能升级完一个马上接着升级下一个最好在两次升级之间给从站留几秒钟的稳定时间尤其是在从站需要复位重启的情况下。否则有些从站还没完全起来你下一个FOE报文就过去了它可能直接不响应。我的做法是每个从站升级完成后读取版本号成功后延时5秒再触发下一个从站的升级。4.3 安全措施与降级方案远程升级固件最怕的是把从站刷成砖。虽然大多数从站固件在刷写时会做校验但总有万一。这里我建议你养成三个习惯一定要保留旧版本固件。升级前用FB_FOE_Read如果从站支持读回固件把当前固件读出来存到本地或者至少留一份旧的固件文件在本地。这样万一新固件出了问题我可以快速刷回旧版本把产线恢复运行。升级前确认从站版本匹配。很多从站固件不是独立的它可能和从站的硬件版本、EEPROM配置、甚至和软件工具链版本绑定。你贸然把一套新固件刷到一个旧硬件版本的从站上很可能刷完以后从站能启动但不工作这种问题比变砖还难排查。升级前把硬件批次、现场环境和厂商的兼容性说明对照一遍很有必要。升级过程要能断尾逃生。升级程序一定要支持手动取消和超时自动取消不能陷入死循环。我在实际程序里加了一个看门狗如果升级状态从开始到完成超过设定的总超时时间比如15分钟程序自动把状态拉回IDLE并且把错误码记录到诊断缓冲区这样下次现场同事排查时能直接看到升级超时而不是程序卡死。4.4 用日志记录整个升级过程最后分享一个实战中非常有用的习惯加诊断日志。升级这种操作不像跑过程数据那样天天发生一旦出了问题你想复现很难。所以我现在的升级程序里每一步状态切换都会记录一条日志包括时间戳、当前状态、目标从站、错误码、文件大小等关键信息。日志写到哪可以写到IPC的本地文本文件也可以写到TwinCAT的路由日志里甚至可以通过ADS发到上位机画面显示。我个人的偏好是写本地文件因为文件日志不依赖上位机在线就算上位机断线了PLC程序自己去跑升级日志照样记录。回头排查时直接看文本日志整个升级过程的每一步都一目了然。// 日志记录示例简化版 IF eState eLastState THEN sLogMessage : CONCAT( Time: , DT_TO_STRING(F_GetSystemTime()), | State: , ENUM_TO_STRING(eState), | Slave: , UINT_TO_STRING(nSlaveAddr), | ErrorID: , UDINT_TO_STRING(nErrorID) ); fbFilePut( sDeviceName : D:\UpdateLog\, sFileName : foe_update.log, sData : sLogMessage, bAppend : TRUE ); eLastState : eState; END_IF这段代码里的F_GetSystemTime需要TwinCAT的实时时钟支持fbFilePut是TwinCAT里写文件的功能库函数具体名称和参数看你的TwinCAT版本。思路很简单就是每次状态变化时把关键信息追加写进一个日志文件。不要小看这个细节很多时候排查问题靠的就是这些当时看起来没用的记录。结尾这套用TwinCAT 3通过EtherCAT FOE给从站远程升级的方案在我的项目里已经用了两年多。从最开始惴惴不安地测试到现在一键刷几十个从站FOE确实帮我省下了大量下现场拆设备的时间。我个人实际操作中的体会是FOE本身不复杂真正决定成败的往往是你对从站特性的了解程度以及升级流程设计的健壮性。如果你项目里正好在选远程升级方案我的建议是优先确认从站固件对FOE的支持情况然后先用单个从站在实验环境完整跑通流程加好日志和校验再推广到产线。这套打法我不敢说是最优解但它是经过现场检验、能落地、能兜底的方案。你如果有更好用的思路或者踩过我没提到的坑欢迎交流。
返回列表