4. 从站协议栈架构:分层模型与状态机

好,咱们今天聊聊EtherCAT从站协议栈的架构。说实话,这个部分是整个驱动开发的骨架。你代码写得再花哨,协议栈架构没搭对,设备就跑不起来。

我个人习惯把协议栈想象成一栋楼。底层是硬件,中间是数据链路,顶层才是用户的应用。每一层各司其职,互不干扰。这样设计的好处是——你换了个PHY芯片,应用层代码不用动;你改了应用逻辑,底层驱动也不用重写。

4.1 协议栈分层模型

EtherCAT从站协议栈,从下往上大致分这么几层:

  • 物理层:负责电气信号的收发。说白了就是PHY芯片和RJ45接口那点事。我见过不少新手在这栽跟头——信号完整性没做好,丢帧率居高不下。
  • 数据链路层:ESC(EtherCAT从站控制器)的核心工作。帧的收发、寻址、FMMU配置、SyncManager管理,全在这层。ESC芯片(比如Beckhoff的ET1100、ET1200,或者国产的AX58100)把大部分脏活累活都干了。
  • 应用层:这才是我们开发者真正要写代码的地方。CoE、SoE、VoE这些协议都在这一层实现。

嗯,这里要注意:数据链路层和应用层之间,有一个协议栈接口。这个接口通常由ESC厂商提供,比如Beckhoff的SSC(Slave Stack Code)工具生成的代码。我建议你直接拿这个代码做二次开发,别自己从头写——太容易踩坑了。

核心观点:分层不是目的,解耦才是。每一层只关心自己的事,出了问题也容易定位。

EtherCAT从站协议栈分层模型 应用层 (Application Layer) CoE (CANopen over EtherCAT) | SoE (SERCOS over EtherCAT) | VoE (Vendor over EtherCAT) 数据链路层 (Data Link Layer) ESC (EtherCAT Slave Controller) | FMMU | SyncManager | 帧处理 物理层 (Physical Layer) PHY芯片 | MII/RMII接口 | 隔离变压器 | RJ45连接器 数据流向 ESC 厂商 提供 SSC 代码 二次 开发

4.2 应用层协议简介:CoE / SoE / VoE

应用层协议,说白了就是主站和从站之间「说同一种语言」。EtherCAT支持好几种应用层协议,最常用的就是CoE。

4.2.1 CoE (CANopen over EtherCAT)

CoE是目前最主流的应用层协议。它把CANopen的那套对象字典(Object Dictionary)搬到了EtherCAT上。你想想看,CANopen的PDO和SDO机制,在EtherCAT上跑得飞起。

  • 对象字典:每个从站都有一个对象字典,索引范围0x1000~0x9FFF。比如0x1000是设备类型,0x1018是标识信息。我习惯把对象字典想象成一个「寄存器地图」——主站通过索引和子索引来读写数据。
  • SDO (服务数据对象):用于非周期性通信。比如配置参数、读取诊断信息。SDO是邮箱通信,有应答机制,可靠但慢。
  • PDO (过程数据对象):用于周期性通信。比如实时采集传感器数据、输出控制指令。PDO是直接映射到FMMU的,速度快,延迟低。

个人经验:我在做伺服驱动器项目时,把所有的控制字和状态字都映射到PDO里。这样主站每个周期都能拿到最新的位置和速度数据,延迟控制在微秒级。如果你用SDO来传实时数据,那基本就告别高性能了。

4.2.2 SoE (SERCOS over EtherCAT)

SoE是从SERCOS接口继承过来的协议。它用IDN(Identification Number)来标识参数,而不是对象字典。SoE在伺服驱动领域用得比较多,尤其是那些从SERCOS迁移到EtherCAT的老牌厂商。

说实话,我接触SoE不多。但我知道一点:如果你用SoE,那你的从站必须支持SERCOS的通信周期和同步机制。嗯,这个门槛比CoE高不少。

4.2.3 VoE (Vendor over EtherCAT)

VoE是留给厂商自定义的协议。说白了,就是EtherCAT给你开了一个「后门」,你可以用自己的私有协议在上面跑数据。VoE通常用于固件升级、厂商特定的诊断功能等。

我曾经在一个项目中用过VoE来传输自定义的调试数据。主站那边写了个Wireshark插件来解析VoE报文。效果还不错,但说实话,能用CoE解决的问题,我建议别碰VoE——兼容性太差了。

协议 数据模型 通信方式 典型应用
CoE 对象字典 (索引+子索引) SDO (邮箱) / PDO (过程数据) 通用I/O、驱动器、传感器
SoE IDN (标识号) 服务通道 / 实时通道 伺服驱动器、运动控制
VoE 厂商自定义 邮箱通信 固件升级、私有诊断

4.3 状态机:Init → Pre-Op → Safe-Op → Op

EtherCAT从站的状态机,是整个协议栈的「心脏」。你想想看,一个从站上电后,不能直接进入运行状态——它得先初始化、配置参数、确认安全,最后才能干活。

状态机一共四个主要状态:InitPre-OpSafe-OpOp。还有一个可选的Boot状态,用于固件升级。

EtherCAT从站状态机 Init 初始化 Pre-Op 预运行 Safe-Op 安全运行 Op 运行 AL状态控制 SyncManager配置 PDO映射完成 错误/请求 错误/请求 复位

4.3.1 Init 状态

上电后,从站首先进入Init状态。在这个状态下,ESC只做最基本的初始化——配置寄存器、检测PHY链路。应用层代码还没跑起来。主站可以读写ESC寄存器,但不能进行邮箱通信。

我记得第一次调试时,从站卡在Init状态出不来。查了半天,发现是PHY的复位引脚没拉高。嗯,硬件上的小问题,软件上折腾了两天。

4.3.2 Pre-Op 状态

从Init切换到Pre-Op,需要主站写AL状态控制寄存器。进入Pre-Op后,邮箱通信就通了。主站可以通过SDO来配置对象字典、设置SyncManager参数。

这个阶段,说白了就是「配置阶段」。主站会告诉从站:你的PDO映射是什么、你的同步模式是什么、你的过程数据长度是多少。我建议你在Pre-Op阶段把所有的配置都做完,别拖到Safe-Op再搞。

4.3.3 Safe-Op 状态

Safe-Op的意思是「安全运行」。在这个状态下,从站开始输出过程数据(PDO),但输出端口保持安全状态——比如伺服驱动器的使能信号是断开的,数字输出模块的输出是0。

输入数据是正常采集的。主站可以读到传感器值,但不会执行控制动作。这个状态常用于「预检」——确认所有数据都正确了,再切到Op。

避坑指南:我曾经在一个项目中,Safe-Op状态下没有正确配置输出映射。结果一切到Op,输出端口直接输出乱码,把执行器给干飞了。从那以后,我每次都在Safe-Op阶段用示波器抓一遍输出波形,确认无误再切Op。

4.3.4 Op 状态

Op是正常运行状态。所有PDO数据都在周期性地交换,输出端口正常驱动负载。从站该干活就干活,该采集就采集。

从Safe-Op切到Op,主站会检查从站的状态机是否就绪。如果从站返回错误,主站会回退到Safe-Op甚至Pre-Op。所以,你的状态机处理函数一定要健壮——别因为一个PDO映射错误就把整个系统搞崩了。

4.3.5 状态转换的代码实现

下面是一个简化的状态机处理函数,我用C语言写的。实际项目中,SSC生成的代码会更复杂,但核心逻辑就是这样。

// 状态机处理函数(简化版)
void AL_StateMachine(void)
{
    uint16_t alControl = ESC_ReadReg(0x0120);  // 读取主站请求的状态
    
    switch (currentState)
    {
        case STATE_INIT:
            if (alControl == 0x02)  // 请求切换到Pre-Op
            {
                // 初始化邮箱通信
                if (MailboxInit() == SUCCESS)
                {
                    currentState = STATE_PRE_OP;
                    ESC_WriteReg(0x0130, 0x02);  // 确认状态
                }
                else
                {
                    ESC_WriteReg(0x0134, ERROR_MAILBOX);  // 报错
                }
            }
            break;
            
        case STATE_PRE_OP:
            if (alControl == 0x03)  // 请求切换到Safe-Op
            {
                // 配置SyncManager和PDO映射
                if (SyncManagerConfig() == SUCCESS)
                {
                    currentState = STATE_SAFE_OP;
                    ESC_WriteReg(0x0130, 0x03);
                }
                else
                {
                    ESC_WriteReg(0x0134, ERROR_SYNC);
                }
            }
            break;
            
        case STATE_SAFE_OP:
            if (alControl == 0x04)  // 请求切换到Op
            {
                // 启动过程数据输出
                if (ProcessDataStart() == SUCCESS)
                {
                    currentState = STATE_OP;
                    ESC_WriteReg(0x0130, 0x04);
                }
                else
                {
                    ESC_WriteReg(0x0134, ERROR_PD);
                }
            }
            break;
            
        case STATE_OP:
            // 正常运行,处理PDO数据
            ProcessDataExchange();
            break;
    }
}

这段代码看着简单,但实际项目中要考虑的东西多得多。比如超时处理、错误恢复、看门狗机制。我建议你在写状态机时,把每个状态的入口和出口都加上日志打印——调试的时候你就知道这有多重要了。

个人习惯:我一般在状态机里加一个「错误状态」分支。如果从站检测到严重错误(比如PHY断连、PDO数据校验失败),直接跳转到错误状态,然后通知主站。这样比在某个状态里死循环要安全得多。

好了,协议栈架构和状态机就聊到这儿。下一节我们会深入ESC的寄存器配置和FMMU映射——那才是真正考验功底的地方。


公众号:蓝海资料掘金营,微信deep3321