【原创】IgH EtherCAT主站详解(四)--并行启动、总体架构及软件分层

一、架构概览

第二章 —IgH EtherCAT Master 1.5.2 总体架构


概览

etherlab 是什么?

IgH EtherCAT Master 1.5.2 开源 EtherCAT 主站实现,它作为 Linux 内核模块运行,完全符合 IEC/PAS 62407 国际标准,提供完整的 EtherCAT 主站协议栈。

核心定位

etherlab 是一个著名的开源工业级 EtherCAT 主站,以 Linux 内核模块形式运行,提供完整的ethercat主站功能,支持多种实时框架(Xenomai、RTAI、RT-Preempt),可在同一台机器上运行多个主站实例,并通过统一的 ecrt.h API 同时服务于内核空间和用户空间应用程序。

系统全景架构

EtherCAT 系统全景架构图

图:motorcortex-etherlab 系统全景架构(用户空间 ↔ 内核空间 ↔ 硬件)

核心能力一览

能力 说明
EtherCAT 协议栈 完整实现 EtherCAT 主站协议,支持所有 Datagram 类型(APRD/APWR/BRD/LRW 等 12 种)
多实时框架支持 Xenomai(内核空间 / 用户空间 RTDM)、RTAI、RT-Preempt,也可无实时扩展运行
原生网卡驱动 r8169、e1000/e1000e、igb、igc、8139too、stmmac 等多款网卡的原生适配版本
通用驱动 generic.c 可适配任何 Linux 内核支持的网卡芯片
过程数据域 (Domain) 多域支持,不同域可使用不同的采样率,自动计算 FMMU/SM 映射
邮箱协议 CoE (SDO/PDO/Emergency)、EoE、FoE、SoE、VoE 全部实现
DC 分布式时钟 纳秒级全网时间同步,支持 SYNC0/SYNC1 信号配置
冗余支持 双网卡冗余,支持环形/双线缆拓扑,自动故障检测与切换
命令行工具 ethercat 工具涵盖 30+ 子命令,用于总线监控、诊断、配置
EoE 虚拟网络 通过 EtherCAT 隧道以太网,支持桥接/路由网络架构

目录结构总览

flowchart LR root["motorcortex-etherlab/"] --> master["master/<br/>核心模块"] root --> devices["devices/<br/>网卡驱动"] root --> include_dir["include/<br/>ecrt.h API"] root --> lib["lib/<br/>用户空间库"] root --> tool["tool/<br/>命令行工具"] root --> examples["examples/<br/>示例代码"] root --> script["script/<br/>启动脚本"] master --> master_core["master.c, slave.c<br/>domain.c, fsm_*.c"] devices --> drv_native["r8169/, e1000e/<br/>igb/, generic.c"] tool --> cmd["Command*.cpp<br/>33+ 子命令"] examples --> ex_xenomai["xenomai/, user/<br/>mini/, dc_user/"]

技术详情

技术能力详解

协议栈完整性

IgH EtherCAT Master 实现了完整的 EtherCAT 协议栈,包括:

  • 数据帧处理:支持所有 12 种 Datagram 寻址模式,帧的组装、发送、接收、解析全流程
  • 从站管理:自动拓扑扫描、从站识别、EEPROM/SII 信息读取、配置生命周期管理
  • 状态机驱动:10+ 个协作式状态机 (FSM) 驱动所有异步操作
  • DC 同步:分布式时钟传播延迟测量、参考时钟选取、漂移补偿、SYNC0/SYNC1 信号配置

实时框架集成

实时框架 内核空间 用户空间 接口
Xenomai ✓ (RTDM) rtdm_dev
RTAI ✓ (RTDM) rtdm_dev
RT-Preempt ✓ (ioctl) cdev
无实时扩展 ✓ (ioctl) cdev

关键源文件索引

目录 主要文件 职责
master/ master.c, slave.c, domain.c, fsm_*.c 核心主站逻辑、10+ 个 FSM 状态机、数据报管理
devices/ r8169/, e1000e/, generic.c 网卡驱动 EtherCAT 适配版本
include/ ecrt.h 实时应用 API 定义(Master/Domain/Slave Config/SDO/VoE)
lib/ master.c, slave_config.c, domain.c 用户空间库,通过 ioctl 与内核通信
tool/ Command*.cpp ethercat 命令行工具,33+ 子命令实现
examples/ mini/, user/, xenomai/, dc_user/ 各平台示例程序与 DC 同步示例
script/ ifup-eoe.sh, sysconfig EoE 网络配置、系统服务脚本

版本信息

版本与许可

  • IgH EtherCAT Master 版本:1.5.2(ecrt.h API 版本 1.5.10)
  • 支持内核版本:2.6.x 至 6.12(本项目以 6.1 为基准)
  • License:GPL v2(内核模块)、LGPL v2.1(用户空间库 lib/)
  • 作者:Florian Pose, Ingenieurgemeinschaft IgH

架构相关章节导航

页面 内容 链接
软件分层架构 四层架构详解 + 数据流向 layer-arch.html
模块依赖关系 核心模块清单 + 依赖全景图 module-dep.html
并行启动与初始化 insmod → 就绪完整流程 startup.html

二、软件分层架构

IgH EtherCAT Master 四层软件架构与数据流


概览

四层架构概览

IgH EtherCAT Master 采用清晰的四层软件架构,从上到下依次为:应用层、库层、内核主站层、驱动层。每一层通过明确定义的接口与相邻层交互,实现了关注点分离。

四层软件架构图

图:IgH EtherCAT Master 四层软件架构

层次 组件 职责
应用层 User Application / ethercat tool 业务逻辑、控制指令、总线监控
库层 lib/ (libecrt.so) ecrt.h API 封装、ioctl 系统调用转换
内核主站层 master/ (ec_master.ko) EtherCAT 协议核心、FSM 状态机、数据管理
驱动层 devices/ (网卡驱动) 网卡硬件抽象、帧的物理收发

技术详情

各层接口详解

以下时序图展示了应用程序从请求主站到周期性数据交换的完整跨层调用链:

sequenceDiagram participant App as 用户应用 participant Lib as lib/ 用户空间库 participant Cdev as 字符设备 (cdev) participant Master as Master 内核模块 participant Device as 网卡驱动 (device.c) participant NIC as 网卡硬件 App->>Lib: ecrt_request_master(0) Lib->>Cdev: ioctl(EC_IOCTL_REQUEST) Cdev->>Master: ecrt_request_master_err() Master-->>Cdev: master 指针 Cdev-->>Lib: 文件描述符 Lib-->>App: ec_master_t* Note over App,NIC: 配置阶段 App->>Lib: ecrt_master_slave_config(...) Lib->>Cdev: ioctl(EC_IOCTL_SLAVE_CONFIG) Cdev->>Master: ecrt_master_slave_config_err() App->>Lib: ecrt_master_activate(master) Lib->>Cdev: ioctl(EC_IOCTL_ACTIVATE) Cdev->>Master: ec_master_enter_operation_phase() Note over App,NIC: 周期性运行阶段 App->>Lib: ecrt_master_send(master) Lib->>Cdev: ioctl(EC_IOCTL_SEND) Cdev->>Master: ecrt_master_send_datagrams() Master->>Device: ec_device_send() Device->>NIC: DMA 发送 NIC-->>Device: 接收完成 Device-->>Master: ec_master_receive_datagrams() App->>Lib: ecrt_master_receive(master) Lib->>Cdev: ioctl(EC_IOCTL_RECEIVE) Master-->>Lib: 处理后的数据 Lib-->>App: 过程数据已更新

应用层

  • 用户空间应用:通过 ecrt.h API 与主站交互,典型流程为 request → config → activate → cyclic send/receive
  • ethercat 工具:命令行工具通过 ioctl 与内核通信,提供总线监控、诊断、配置功能
  • 内核空间应用:内核模块可直接调用 ecrt.h 的内核 API,无需 ioctl 开销

库层 (lib/)

  • 将用户空间 API 调用转换为 ioctl 系统调用,通过 /dev/EtherCAT* 字符设备与内核通信
  • 关键文件:lib/master.clib/slave_config.clib/domain.c
  • 过程数据通过 mmap 内存映射传递,避免内核-用户空间数据拷贝开销
  • License 为 LGPL v2.1,允许商业应用链接

内核主站层 (master/)

  • EtherCAT 协议核心实现:帧组装/解析、数据报队列管理、FSM 状态机驱动
  • 10+ 个协作式 FSM 状态机管理所有异步操作(扫描、配置、通信)
  • 过程数据 Domain 管理:自动 FMMU 地址分配、数据报成对管理
  • 从站配置与监控:配置请求生命周期、AL 状态跟踪
  • 关键入口:module.c(模块加载)、master.c(主站核心)

驱动层 (devices/)

  • 抽象网卡硬件操作,为上层提供统一的 ec_device 接口
  • 提供 poll 函数接口(ec_pollfunc_t),主站通过轮询获取接收数据
  • 支持原生驱动(r8169、e1000e、igb、igc、8139too、stmmac 等)和通用驱动(generic.c)
  • 发送使用 sk_buff TX Ring(16 个槽位),DMA 传递到网卡

完整数据流向图

flowchart TD subgraph 用户空间 A["用户应用<br/>ecrt_master_send()"] T["ethercat 工具<br/>ioctl()"] end subgraph 库层 L["lib/master.c<br/>ioctl 封装"] end subgraph 内核主站层 M["master.c<br/>数据报队列管理"] D["datagram.c<br/>数据报组装/解析"] F["fsm_master.c<br/>状态机驱动"] DM["domain.c<br/>过程数据映射"] end subgraph 驱动层 DEV["device.c<br/>网卡抽象"] DRV["网卡驱动<br/>(r8169, e1000e...)"] end subgraph 硬件 NIC["网卡 NIC"] SLAVES["EtherCAT 从站"] end A -->|"API 调用"| L T -->|"ioctl"| L L -->|"系统调用"| M M --> D M --> F M --> DM D -->|"帧数据"| DEV F -->|"FSM 数据报"| DEV DEV --> DRV DRV -->|"DMA"| NIC NIC -->|"EtherCAT 帧"| SLAVES SLAVES -->|"返回帧"| NIC NIC --> DRV DRV --> DEV DEV --> M M --> L L --> A

深入源码

ecrt.h API 到内核的映射

以下展示用户空间 API 调用如何经库层传递到内核处理的完整链路:

include/ecrt.h + lib/master.c + master/module.c — API 调用链

// ========== include/ecrt.h — 用户空间 API 声明 ==========
ec_master_t *ecrt_request_master(unsigned int master_index);

// ========== lib/master.c — 用户空间库封装 ==========
ec_master_t *ecrt_request_master(unsigned int master_index) {
    ec_master_t *master = ecrt_open_master(master_index);
    // ecrt_open_master() 内部:
    //   fd = open("/dev/EtherCAT0", O_RDWR);
    //   master->fd = fd;

    if (master) {
        if (ecrt_master_reserve(master)) {
            // ecrt_master_reserve() 内部:
            //   ioctl(master->fd, EC_IOCTL_REQUEST, NULL);
            ecrt_release_master(master);
            return NULL;
        }
    }
    return master;
}

// ========== master/module.c — 内核模块处理 ==========
ec_master_t *ecrt_request_master_err(unsigned int master_index) {
    ec_master_t *master;

    // 1. 检查 master_index 有效性
    if (master_index >= master_count)
        return ERR_PTR(-EINVAL);
    master = &masters;[master_index];

    // 2. 获取 master_sem 信号量
    ec_lock_down_interruptible(&master;_sem);

    // 3. 检查 master->reserved 标志(互斥访问)
    if (master->reserved) {
        ec_lock_up(&master;_sem);
        return ERR_PTR(-EBUSY);
    }
    master->reserved = 1;
    ec_lock_up(&master;_sem);

    // 4. 检查 phase == EC_IDLE(设备已绑定)
    if (master->phase != EC_IDLE)
        return ERR_PTR(-ENODEV);

    // 5. 进入 OPERATION 阶段
    ec_master_enter_operation_phase(master);

    return master;
}

关键结构体关系

classDiagram class ec_master { +unsigned int index +unsigned int reserved +ec_master_phase_t phase +ec_device_t devices +ec_slave_t slaves +list_head configs +list_head domains +ec_fsm_master_t fsm +task_struct thread +list_head datagram_queue } class ec_device { +ec_master_t master +net_device dev +ec_pollfunc_t poll +sk_buff tx_skb +uint8_t link_state +u64 tx_count +u64 rx_count } class ec_domain { +ec_master_t master +size_t data_size +uint8_t data +list_head fmmu_configs +list_head datagram_pairs +uint16_t working_counter } class ec_slave { +ec_master_t master +uint16_t ring_position +uint16_t station_address +ec_slave_config_t config +uint8_t current_state +ec_fsm_slave_t fsm } class ec_slave_config { +uint16_t alias +uint16_t position +uint32_t vendor_id +uint32_t product_code +ec_sync_config_t sync_configs +ec_fmmu_config_t fmmu_configs +list_head sdo_configs +list_head voe_handlers } ec_master "1" --> "*" ec_device : devices ec_master "1" --> "*" ec_slave : slaves ec_master "1" --> "*" ec_domain : domains ec_slave "1" --> "0..1" ec_slave_config : config

主站 Phase 转换

主站生命周期中经历三个阶段 (Phase),每个阶段有明确的职责和线程模型:

Phase 触发条件 运行线程 核心职责
EC_ORPHANED ec_master_init() 等待网卡设备绑定,不能执行任何总线操作
EC_IDLE ecdev_offer() MAC 匹配后 ec_master_enter_idle_phase() idle_thread Master FSM 循环执行,自动总线扫描,从站状态监控
EC_OPERATION ecrt_request_master() ec_master_enter_operation_phase() operation_thread 周期性数据交换,从站配置下发,FSM 执行配置/监控
stateDiagram-v2 [*] --> ORPHANED: ec_master_init() ORPHANED --> IDLE: ecdev_offer() MAC 匹配成功 IDLE --> OPERATION: ecrt_request_master() OPERATION --> IDLE: ecrt_release_master() IDLE --> ORPHANED: 网卡设备断开 state ORPHANED { [*] --> 等待设备 等待设备: 无网卡绑定 等待设备: 不能执行总线操作 } state IDLE { [*] --> 空闲线程运行 空闲线程运行: Master FSM 循环 空闲线程运行: 自动总线扫描 空闲线程运行: 从站状态监控 } state OPERATION { [*] --> 运行线程运行 运行线程运行: 周期性数据交换 运行线程运行: 从站配置下发 运行线程运行: FSM 配置/监控 }

三、模块依赖关系

核心模块清单、职责与依赖关系


概览

模块依赖全景

IgH EtherCAT Master 由 40+ 个源文件组成,可分为四大类:核心管理、FSM 状态机、配置管理、通信协议。下图展示了核心模块之间的调用与依赖关系:

模块依赖全景图

图:IgH EtherCAT Master 核心模块依赖关系全景

模块组织原则

所有模块围绕 master.c 这一核心编排,采用协作式状态机 (FSM) 驱动异步操作。FSM 按层级嵌套:Master FSM → Slave FSM → 协议 FSM(CoE/EoE/FoE/SoE)。配置模块(FMMU、Sync Manager、PDO)由 Slave Config FSM 在配置阶段驱动。


技术详情

核心模块清单

模块 源文件 职责
Module 入口 module.c 内核模块加载/卸载,MAC 地址解析,ecdev_offer() 设备匹配
Master 核心 master.c / master.h 主站生命周期管理(ORPHANED/IDLE/OPERATION),线程调度,数据报收发
Device 设备 device.c / device.h 网卡设备抽象层,sk_buff TX Ring 管理,帧发送/接收
Domain 域 domain.c / domain.h 过程数据域管理,FMMU 地址分配,数据报对管理
Slave 从站 slave.c / slave.h 从站信息管理,AL 状态监控,SII 数据缓存
Slave Config slave_config.c / slave_config.h 从站配置请求,SDO/PDO/SM/FMMU 配置管理,VoE/SDO/FoE handler
Datagram datagram.c / datagram.h 数据报构造/解析,12 种寻址模式,WKC 检查,队列管理
FMMU Config fmmu_config.c / fmmu_config.h FMMU 配置(逻辑地址 → 物理地址映射),读/写使能控制
Sync/SM Config sync_config.c / sync.h 同步管理器配置,邮箱/过程数据缓冲区控制
PDO pdo.c / pdo_entry.c / pdo_list.c PDO 映射表管理,PDO 分配/映射配置
Mailbox mailbox.c / mailbox.h 邮箱通信基础设施,双缓冲区握手机制,协议复用
Ethernet/EoE ethernet.c / eoe_request.c EoE 虚拟网络接口,Linux net_device 注册,帧分片/重组
Cdev cdev.c / cdev.h 字符设备(/dev/EtherCAT*),ioctl 命令分发

FSM 状态机模块

FSM 是 IgH EtherCAT Master 的核心驱动机制,所有异步操作(扫描、配置、通信)都通过协作式状态机实现。每个 FSM 在每个周期执行一步,不会阻塞。

FSM 源文件 状态数 职责 调用者
Master FSM fsm_master.c 15 总线监控,触发扫描/配置,管理 SII 写入请求 idle_thread / operation_thread
Slave FSM fsm_slave.c 12 单个从站状态监控,请求分发到子 FSM Master FSM
Slave Config fsm_slave_config.c 24 从站完整配置流程(Init → PreOp → SafeOp → Op) Master FSM
Slave Scan fsm_slave_scan.c 19 从站扫描与识别,读取 SII,构建 PDO 映射 Master FSM
State Change fsm_change.c 9 AL 状态转换请求-确认机制 Slave Config FSM
CoE FSM fsm_coe.c 21 CoE SDO 上传/下载,分段传输,Emergency 消息 Slave FSM
EoE FSM fsm_eoe.c 7 EoE 数据帧分片/重组收发 EoE 线程 / Slave FSM
FoE FSM fsm_foe.c 14 FoE 文件传输,固件升级 Slave FSM
SoE FSM fsm_soe.c 12 SoE IDN 读写操作 Slave FSM
SII FSM fsm_sii.c 8 EEPROM/SII 读/写操作 Slave Scan FSM, Master FSM
PDO FSM fsm_pdo.c / fsm_pdo_entry.c PDO 分配/映射配置读取 Slave Scan FSM

模块间关键关系

classDiagram direction TB class module_c { +ec_init_module() +ec_cleanup_module() +ecdev_offer() } class master_c { +ec_master_init() +ec_master_clear() +idle_thread() +operation_thread() +ec_master_send_datagrams() +ec_master_receive_datagrams() } class device_c { +ec_device_init() +ec_device_attach() +ec_device_poll() +ec_device_send() } class domain_c { +ec_domain_init() +ec_domain_add_fmmu_config() +ec_domain_finish() } class slave_c { +ec_slave_init() +ec_slave_clear() } class slave_config_c { +ec_slave_config_init() +ec_slave_config_attach() +ec_slave_config_process() } class datagram_c { +ec_datagram_init() +ec_datagram_prealloc() +ec_datagram_create() } class fsm_master_c { +ec_fsm_master_init() +ec_fsm_master_exec() } class fsm_slave_c { +ec_fsm_slave_init() +ec_fsm_slave_exec() } module_c --> master_c : 创建/管理 master_c --> device_c : 收发帧 master_c --> domain_c : 过程数据 master_c --> slave_c : 从站管理 master_c --> datagram_c : 数据报队列 master_c --> fsm_master_c : 状态机驱动 slave_c --> slave_config_c : 配置绑定 fsm_master_c --> fsm_slave_c : 从站 FSM 分发

深入源码

头文件包含关系

以下展示 master/master.h 的核心依赖,它是整个内核主站层的中枢头文件:

master/master.h — 核心头文件包含

// master/master.h 的核心依赖
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/kthread.h>

#include "device.h"        // 网卡设备抽象 (ec_device)
#include "domain.h"        // 过程数据域 (ec_domain)
#include "ethernet.h"      // EoE 处理 (ec_eoe_handler)
#include "fsm_master.h"    // Master 状态机 (ec_fsm_master)
#include "locks.h"         // 同步锁抽象 (ec_lock_t)
#include "cdev.h"          // 字符设备 (ec_cdev)

#ifdef EC_RTDM
#include "rtdm.h"          // RTDM 设备接口
#endif

模块间数据流

下图展示了数据在各模块间的流转路径,从用户请求到最终硬件发送:

flowchart LR subgraph 入口 MOD[module.c] end subgraph 核心 MAS[master.c] DEV[device.c] DOM[domain.c] SLA[slave.c] SLC[slave_config.c] DAT[datagram.c] end subgraph FSM FM[fsm_master.c] FS[fsm_slave.c] FSC[fsm_slave_config.c] FSS[fsm_slave_scan.c] FC[fsm_change.c] FCOE[fsm_coe.c] FEOE[fsm_eoe.c] FFOE[fsm_foe.c] FSOE[fsm_soe.c] FSII[fsm_sii.c] end subgraph 配置 FMMU[fmmu_config.c] SYNC[sync_config.c] PDO[pdo.c / pdo_entry.c] MBX[mailbox.c] end MOD -->|"创建"| MAS MAS -->|"管理"| DEV MAS -->|"管理"| DOM MAS -->|"管理"| SLA MAS -->|"管理"| DAT MAS -->|"驱动"| FM FM -->|"触发"| FS FM -->|"触发"| FSS FS -->|"分发"| FCOE FS -->|"分发"| FEOE FS -->|"分发"| FFOE FS -->|"分发"| FSOE FSC -->|"使用"| FC FSC -->|"使用"| FCOE FSC -->|"使用"| FMMU FSC -->|"使用"| SYNC FSS -->|"使用"| FSII DOM -->|"使用"| FMMU SLC -->|"使用"| PDO FCOE -->|"使用"| MBX FEOE -->|"使用"| MBX FFOE -->|"使用"| MBX FSOE -->|"使用"| MBX

深入了解各模块

四、并行启动与初始化

第二章 — 从 insmod 到主站就绪,各从站并行完成状态切换


概览

启动流程三阶段

IgH EtherCAT Master 的启动过程分为三个清晰的阶段,每个阶段由不同的事件触发。其中最关键的第三阶段——从站配置——采用并行启动机制:各个 EtherCAT 从站独立且并行地从 INIT 状态切换到 OP 状态,从站之间无耦合,互不等待。

阶段 触发命令 关键动作
1. 内核模块加载 insmod ec_master.ko main_devices=XX:XX... 创建主站实例,分配字符设备号,等待网卡设备
2. 网卡驱动绑定 insmod r8169-ethercat.ko 网卡驱动调用 ecdev_offer(),MAC 匹配后绑定设备
3. 应用请求主站 ecrt_request_master(0) 主站进入 OPERATION 阶段,开始周期性数据交换

核心概念:设备匹配机制

主站启动时处于 ORPHANED(孤立)状态,等待网卡驱动通过 ecdev_offer() 上报设备。当网卡的 MAC 地址与模块参数 main_devices 中指定的地址匹配时,设备绑定成功,主站进入 IDLE 阶段并自动开始总线扫描。

整体启动流程

flowchart TD A["insmod ec_master.ko<br/>main_devices=XX:XX:XX"] --> B["ec_init_module()<br/>分配主站实例"] B --> C["等待网卡设备<br/>(ORPHANED 阶段)"] C --> D["insmod r8169-ethercat.ko<br/>网卡驱动加载"] D --> E["ecdev_offer()<br/>MAC 地址匹配"] E --> F["ec_device_attach()<br/>设备绑定"] F --> G["IDLE 阶段<br/>空闲线程启动"] G --> H["自动总线扫描<br/>从站发现与识别"] H --> I["应用程序<br/>ecrt_request_master()"] I --> J["OPERATION 阶段<br/>运行线程启动"] J --> K["从站并行配置<br/>各从站独立切换状态"] K --> K1["从站 0: Init→PreOp→SafeOp→Op"] K --> K2["从站 1: Init→PreOp→SafeOp→Op"] K --> K3["从站 N: Init→PreOp→SafeOp→Op"] style A fill:#ebf8ff,stroke:#90cdf4 style C fill:#fff5f5,stroke:#feb2b2 style G fill:#f0fff4,stroke:#9ae6b4 style J fill:#faf5ff,stroke:#d6bcfa style K fill:#fffff0,stroke:#fbd38d style K1 fill:#f0fff4,stroke:#9ae6b4 style K2 fill:#f0fff4,stroke:#9ae6b4 style K3 fill:#f0fff4,stroke:#9ae6b4

技术详情

Phase 详解

EC_ORPHANED 阶段

  • 主站已通过 ec_master_init() 完成初始化(分配数据结构、初始化 FSM、创建字符设备)
  • 但尚未有网卡设备绑定,master->devices[EC_DEVICE_MAIN].dev == NULL
  • 不能执行任何总线操作,无线程运行
  • 等待网卡驱动调用 ecdev_offer() 提供设备

EC_IDLE 阶段

  • 网卡设备通过 ec_device_attach() 绑定成功
  • idle_thread 内核线程启动,循环执行:
    • 调用 ec_fsm_master_exec() 驱动 Master FSM
    • Master FSM 自动执行总线扫描、从站状态监控
    • 处理 SII 写入请求等异步操作
  • 可通过 ethercat 工具查看总线信息

EC_OPERATION 阶段

  • 应用程序通过 ecrt_request_master() 独占请求主站
  • operation_thread 运行线程启动(或由应用程序自行驱动 FSM)
  • 应用程序通过 ecrt_master_activate() 触发从站配置
  • 从站经历 Init → Pre-Op → Safe-Op → Op 状态转换
  • 应用程序接管周期性 ecrt_master_send() / ecrt_master_receive()

从站并行状态切换(并行启动)

在 OPERATION 阶段,主站通过 fsm_slave_config 为每个从站下发配置,驱动其状态从 INIT 逐步切换到 OP。IgH 的核心设计是:各个从站的状态切换过程相互独立、无耦合,每个从站按照自身节奏完成 INIT → Pre-Op → Safe-Op → Op 的转换,不需要等待其他从站。

并行启动的含义

"并行启动"指的是总线上多个 EtherCAT 从站各自独立地完成状态切换。从站 A 的状态转换不依赖于从站 B 的状态,从站之间不存在时序耦合。即使某个从站因配置错误停留在 Pre-Op,其他从站仍可正常进入 Op 状态并开始过程数据交换。

以下时序图展示了三个从站并行完成状态切换的过程:

sequenceDiagram participant Master as Master FSM participant S0 as 从站 0 (伺服) participant S1 as 从站 1 (I/O) participant S2 as 从站 2 (传感器) rect rgb(235, 244, 255) Note over Master,S2: 各从站独立完成 INIT → Pre-Op Master->>S0: fsm_slave_config: INIT → Pre-Op Master->>S1: fsm_slave_config: INIT → Pre-Op Master->>S2: fsm_slave_config: INIT → Pre-Op S0-->>Master: Pre-Op 就绪 S1-->>Master: Pre-Op 就绪 S2-->>Master: Pre-Op 就绪 end rect rgb(240, 255, 244) Note over Master,S2: 各从站独立完成 Pre-Op → Safe-Op Master->>S0: fsm_slave_config: Pre-Op → Safe-Op Master->>S1: fsm_slave_config: Pre-Op → Safe-Op Master->>S2: fsm_slave_config: Pre-Op → Safe-Op S0-->>Master: Safe-Op 就绪 S1-->>Master: Safe-Op 就绪 S2-->>Master: Safe-Op 就绪 end rect rgb(250, 245, 255) Note over Master,S2: 各从站独立完成 Safe-Op → OP Master->>S0: fsm_slave_config: Safe-Op → OP Master->>S1: fsm_slave_config: Safe-Op → OP Master->>S2: fsm_slave_config: Safe-Op → OP S0-->>Master: OP 就绪 (过程数据可读写) S1-->>Master: OP 就绪 (过程数据可读写) Note over S2: 从站 2 配置异常<br/>停留在 Safe-Op Note over S0,S1: 从站 0、1 不受影响<br/>正常进入 OP end

多主站并行运行

IgH 支持在同一台机器上同时运行最多 32 个主站实例,每个主站绑定不同的网卡。以下是双主站并行运行的时序:

sequenceDiagram participant User as 管理员 participant Module as ec_master 模块 participant M0 as Master 0 participant M1 as Master 1 participant Drv as 网卡驱动 participant App as 应用程序 User->>Module: insmod ec_master.ko<br/>main_devices=MAC0,MAC1 Module->>M0: ec_master_init(0, MAC0) Module->>M1: ec_master_init(1, MAC1) Note over M0,M1: ORPHANED 阶段,等待设备 User->>Drv: insmod r8169-ethercat.ko Drv->>Module: ecdev_offer(net_dev0, poll, module) Module->>M0: MAC 匹配!ec_device_attach() Note over M0: 进入 IDLE 阶段 Drv->>Module: ecdev_offer(net_dev1, poll, module) Module->>M1: MAC 匹配!ec_device_attach() Note over M1: 进入 IDLE 阶段 Note over M0,M1: 各自独立运行空闲线程<br/>各自独立扫描总线 App->>M0: ecrt_request_master(0) Note over M0: 进入 OPERATION 阶段<br/>各从站并行状态切换 App->>M1: ecrt_request_master(1) Note over M1: 进入 OPERATION 阶段<br/>各从站并行状态切换

模块参数说明

参数 类型 说明 示例
main_devices charp[] 主网卡 MAC 地址列表,逗号分隔 00:11:22:33:44:55
backup_devices charp[] 备用网卡 MAC 地址(冗余模式) 00:aa:bb:cc:dd:ee
debug_level uint 调试级别(0=关闭,1=基本信息,2=详细) 1
pcap_size ulong PCAP 调试缓冲区大小(字节),0=禁用 0
eoe_interfaces charp[] EoE 接口名称列表 eoe0
eoe_autocreate bool 自动创建 EoE 接口(默认 true) 1

典型加载命令

Shell — 典型的主站加载流程

# 1. 加载 EtherCAT 主站模块(绑定两个网卡)
insmod ec_master.ko main_devices=00:11:22:33:44:55,00:aa:bb:cc:dd:ee

# 2. 加载 EtherCAT 适配的网卡驱动
insmod r8169-ethercat.ko

# 查看主站状态
ethercat master

# 输出示例:
# Master0
#   Phase:  Idle
#   Slaves: 3
#   ...
# Master1
#   Phase:  Idle
#   Slaves: 0

深入源码

ec_init_module() 完整流程

以下逐行分析 master/module.c 中的模块初始化函数:

master/module.c — ec_init_module()

int __init ec_init_module(void)
{
    int i, ret = 0;

    // 1. 打印版本信息
    EC_INFO("Master driver %s\n", EC_MASTER_VERSION);

    // 2. 初始化全局主站信号量(保护 masters[] 数组)
    ec_lock_init(&master;_sem);

    // 3. 分配字符设备号区域(/dev/EtherCAT0, /dev/EtherCAT1, ...)
    if (master_count) {
        if (alloc_chrdev_region(&device;_number,
                    0, master_count, "EtherCAT")) {
            EC_ERR("Failed to obtain device number(s)!\n");
            ret = -EBUSY;
            goto out_return;
        }
    }

    // 4. 创建 sysfs 设备类(/sys/class/EtherCAT/)
    class = class_create(THIS_MODULE, "EtherCAT");

    // 5. 清零 MAC 地址数组并解析模块参数
    memset(macs, 0x00, sizeof(uint8_t) * MAX_MASTERS * 2 * ETH_ALEN);
    for (i = 0; i < master_count; i++) {
        ret = ec_mac_parse(macs[i][0], main_devices[i], 0);
        if (ret) goto out_class;

        if (i < backup_count) {
            ret = ec_mac_parse(macs[i][1], backup_devices[i], 1);
            if (ret) goto out_class;
        }
    }

    // 6. 初始化静态变量(Datagram 索引计数器等)
    ec_master_init_static();

    // 7. 分配并初始化所有主站实例
    masters = kmalloc(sizeof(ec_master_t) * master_count, GFP_KERNEL);
    for (i = 0; i < master_count; i++) {
        ret = ec_master_init(&masters;[i], i, macs[i][0], macs[i][1],
                    device_number, class, debug_level);
        if (ret) goto out_free_masters;
    }

    EC_INFO("%u master%s waiting for devices.\n",
            master_count, (master_count == 1 ? "" : "s"));
    return ret;

out_free_masters:
    for (i--; i >= 0; i--)
        ec_master_clear(&masters;[i]);
    kfree(masters);
out_class:
    class_destroy(class);
out_cdev:
    if (master_count)
        unregister_chrdev_region(device_number, master_count);
out_return:
    return ret;
}

ecdev_offer() 设备匹配机制

当网卡驱动加载后,它会在 probe 阶段调用 ecdev_offer() 将自己"推销"给 EtherCAT 主站模块:

master/module.c — ecdev_offer()

ec_device_t *ecdev_offer(
        struct net_device *net_dev,   // 网卡驱动的 net_device
        ec_pollfunc_t poll,           // 设备的 poll 函数
        struct module *module)        // 网卡驱动模块指针
{
    ec_master_t *master;
    unsigned int i, dev_idx;

    // 遍历所有主站实例
    for (i = 0; i < master_count; i++) {
        master = &masters;[i];

        // 获取设备信号量(保护并发访问)
        ec_lock_down_interruptible(&master-;>device_sem);

        // 遍历主站的设备槽位(主设备 + 可选的备份设备)
        for (dev_idx = EC_DEVICE_MAIN;
                dev_idx < ec_master_num_devices(master); dev_idx++) {

            // 检查设备槽位是否空闲 且 MAC 地址匹配(或为广播地址)
            if (!master->devices[dev_idx].dev
                && (ec_mac_equal(master->macs[dev_idx], net_dev->dev_addr)
                    || ec_mac_is_broadcast(master->macs[dev_idx]))) {

                EC_INFO("Accepting %s as %s device for master %u.\n",
                        str, ec_device_names[dev_idx != 0], master->index);

                // 绑定设备到主站
                ec_device_attach(&master-;>devices[dev_idx],
                        net_dev, poll, module);

                // 重命名网卡接口:eth0 → ecm0 (main) 或 ecb0 (backup)
                snprintf(net_dev->name, IFNAMSIZ, "ec%c%u",
                        ec_device_names[dev_idx != 0][0], master->index);

                return &master-;>devices[dev_idx]; // 匹配成功
            }
        }
        ec_lock_up(&master-;>device_sem);
    }
    return NULL; // 无匹配的主站,拒绝此设备
}

Phase 转换状态机

stateDiagram-v2 [*] --> ORPHANED : ec_master_init ORPHANED --> IDLE : ecdev_offer MAC 匹配成功 IDLE --> OPERATION : ecrt_request_master OPERATION --> IDLE : ecrt_release_master IDLE --> ORPHANED : 网卡设备断开 state ORPHANED { [*] --> 等待设备 等待设备 : 无网卡绑定,不能执行总线操作 } state IDLE { [*] --> 空闲线程运行 空闲线程运行 : Master FSM 循环执行,自动扫描总线,监控从站状态 } state OPERATION { [*] --> 从站并行配置 从站并行配置 : 各从站独立 Init-PreOp-SafeOp-Op,互不等待,周期性数据交换 }

线程模型详解

线程 运行阶段 函数 职责
idle_thread IDLE ec_master_idle_thread() 循环调用 Master FSM,发送/接收帧,执行总线扫描和状态监控
operation_thread OPERATION ec_master_operation_thread() 当应用未自行驱动 FSM 时运行,执行 FSM 和数据报处理
eoe_thread IDLE/OPERATION ec_master_eoe_thread() 处理 EoE 数据收发(需启用 EC_EOE 编译选项)

应用程序接管 FSM 驱动

在 OPERATION 阶段,应用程序可以通过 ecrt_master_send()ecrt_master_receive() 自行驱动数据报收发。此时 operation_thread 仍会运行,负责处理非应用数据报(如 SDO 请求、从站配置等外部 FSM 操作)。

深入了解

posted @ 2026-04-14 21:18  沐多  阅读(678)  评论(1)    收藏  举报