【原创】IgH EtherCAT主站详解(十四)--应用层与协议栈调用链

应用层协议栈调用链

4.5 — 从用户空间 API 到内核再到硬件的完整调用路径


概览

从 API 到硬件的数据流

用户应用程序通过 ecrt.h 定义的 API 与 EtherCAT 主站交互。这些 API 调用经过 用户空间库 (lib/) → ioctl 系统调用 → 内核主站模块 (master/) → 网卡驱动 → EtherCAT 总线 的完整路径。本节展示 4 条核心调用链的完整路径。

4 条核心调用链

调用链 1: ecrt_master_activate() — 主站激活
调用链 2: ecrt_slave_config_reg_pdo_entry() — PDO 注册
调用链 3: SDO 读写 — ecrt_master_sdo_download/upload()
调用链 4: 周期性 IO — ecrt_master_send/receive()


技术详情

调用链 1: 主站激活 (ecrt_master_activate)

sequenceDiagram participant APP as 用户应用 participant LIB as lib/master.c participant IOCTL as ioctl 系统调用 participant MST as master/master.c participant DOM as master/domain.c participant THREAD as 内核线程 APP->>LIB: ecrt_master_activate(master) LIB->>IOCTL: ioctl(fd, EC_IOCTL_ACTIVATE) IOCTL->>MST: ecrt_master_activate() MST->>MST: 遍历所有 Domain loop 每个 Domain MST->>DOM: ec_domain_finish(domain, offset) DOM->>DOM: 计算 FMMU 逻辑地址 DOM->>DOM: 分配过程数据内存 end MST->>MST: 停止 IDLE 线程 MST->>MST: 设置应用回调 (send_cb/receive_cb) MST->>MST: ec_master_enter_operation_phase() Note over MST: IDLE → OPERATION MST->>THREAD: 启动 EtherCAT-OP 线程 MST-->>LIB: 返回成功 LIB-->>APP: 激活完成

调用链 2: PDO 入口注册 (ecrt_domain_reg_pdo_entry)

sequenceDiagram participant APP as 用户应用 participant SC_LIB as lib/slave_config.c participant D_LIB as lib/domain.c participant IOCTL as ioctl 系统调用 participant MST as master/master.c participant SC as master/slave_config.c participant DOM as master/domain.c APP->>SC_LIB: ecrt_slave_config_pdos(sc, syncs) SC_LIB->>IOCTL: ioctl(fd, EC_IOCTL_SC_SYNC) IOCTL->>SC: ecrt_slave_config_sync_manager() SC->>SC: 配置 Sync Manager 参数 APP->>D_LIB: ecrt_domain_reg_pdo_entry(domain, sc, index, subindex) D_LIB->>IOCTL: ioctl(fd, EC_IOCTL_DOMAIN_REG_PDO_ENTRY) IOCTL->>DOM: ecrt_domain_reg_pdo_entry() DOM->>DOM: 查找 FMMU 配置 DOM->>DOM: 计算逻辑地址偏移 DOM-->>D_LIB: 返回 bit_position D_LIB-->>APP: 返回 PDO 偏移量

调用链 3: SDO 读写

sequenceDiagram participant APP as 用户应用 participant LIB as lib/master.c participant IOCTL as ioctl 系统调用 participant MST as master/master.c participant REQ as SDO Request participant FSM as fsm_coe.c participant BUS as EtherCAT 总线 APP->>LIB: ecrt_master_sdo_download(master, position, index, subindex, data, size) LIB->>IOCTL: ioctl(fd, EC_IOCTL_SLAVE_SDO_DOWNLOAD) IOCTL->>MST: ec_master_sdo_download() MST->>REQ: 创建 SDO 请求 MST->>MST: 将请求加入从站的 sdo_requests 队列 loop 等待完成 MST->>FSM: ec_fsm_coe_exec() (IDLE/OP 线程) FSM->>BUS: CoE SDO Download 请求 (邮箱) BUS-->>FSM: SDO Download 响应 FSM-->>MST: 请求完成 end MST-->>LIB: 返回结果 LIB-->>APP: 0=成功 / -errno

调用链 4: 周期性过程数据 IO

flowchart TD subgraph "用户空间 — 实时循环" A["应用程序周期函数"] --> B["ecrt_master_receive(master)"] B --> C["ecrt_domain_process(domain)"] C --> D["读取过程数据<br/>domain_data + offset"] D --> E["控制逻辑处理"] E --> F["写入过程数据<br/>domain_data + offset"] F --> G["ecrt_domain_process(domain)"] G --> H["ecrt_master_send(master)"] H --> I["等待下一个周期"] I --> A end subgraph "内核空间 — 数据路径" B -.->|"ioctl → receive_cb"| J["ec_master_receive_datagrams()"] J --> K["匹配 Datagram + 更新 WKC"] K --> L["拷贝数据到 domain_data"] H -.->|"ioctl → send_cb"| M["ec_master_send_datagrams()"] M --> N["从 domain_data 拷贝到 Datagram"] N --> O["填充帧头 + 串联发送"] O --> P["ec_device_send()"] end style A fill:#ebf8ff,stroke:#90cdf4 style E fill:#c6f6d5,stroke:#68d391

API 函数清单 (ecrt.h)

分类 函数 用途
Master ecrt_master_create() 打开主站设备
ecrt_master_activate() 激活主站进入 OPERATION
ecrt_master_deactivate() 反激活主站回到 IDLE
ecrt_master_send() 发送所有 Datagram
ecrt_master_receive() 接收并处理响应
Domain ecrt_master_create_domain() 创建过程数据域
ecrt_domain_reg_pdo_entry() 注册 PDO 入口
ecrt_domain_data() 获取过程数据指针
Slave Config ecrt_master_slave_config() 获取从站配置句柄
ecrt_slave_config_pdos() 配置 PDO 映射
ecrt_slave_config_dc() 配置 DC 同步参数
SDO ecrt_master_sdo_download() 下载 SDO (写从站)
ecrt_master_sdo_upload() 上传 SDO (读从站)

深入源码

用户空间库架构

源文件: lib/master.c, lib/slave_config.c, lib/domain.c

用户空间库是对 ecrt.h API 的封装,每个函数内部通过 ioctl() 系统调用进入内核。库函数主要职责是:

  1. 打开主站字符设备 /dev/EtherCAT*
  2. 将用户参数打包为 ioctl 命令和数据结构
  3. 调用 ioctl(fd, EC_IOCTL_*, &data;) 进入内核
  4. 等待内核完成操作并返回结果

ioctl 命令分发

源文件: master/cdev.c

内核侧的字符设备驱动处理 ioctl 命令:

ioctl 命令 内核函数 用途
EC_IOCTL_ACTIVATE ecrt_master_activate() 激活主站
EC_IOCTL_DEACTIVATE ecrt_master_deactivate() 反激活
EC_IOCTL_SEND ecrt_master_send() 发送帧
EC_IOCTL_RECEIVE ecrt_master_receive() 接收帧
EC_IOCTL_SC_SYNC ecrt_slave_config_sync_manager() 配置 SM
EC_IOCTL_DOMAIN_REG ecrt_domain_reg_pdo_entry() 注册 PDO
EC_IOCTL_SLAVE_SDO_DOWNLOAD ec_master_sdo_download() SDO 写
EC_IOCTL_SLAVE_SDO_UPLOAD ec_master_sdo_upload() SDO 读

ecrt_master_send() 完整调用路径

位置: master/master.c:3057

flowchart TD A["ecrt_master_send(master)<br/>lib/master.c"] --> B["ioctl(fd, EC_IOCTL_SEND)"] B --> C["ec_cdev_ioctl_send()<br/>master/cdev.c"] C --> D["ecrt_master_send(master)<br/>master/master.c:3057"] D --> E["遍历 dev_idx: MAIN → BACKUP"] E --> F["ec_master_send_datagrams()<br/>master/master.c:1171"] F --> G["填充 EtherCAT 帧头<br/>(Length + Type)"] G --> H["遍历 datagram_queue<br/>串联多个 Datagram"] H --> I["检查大小限制<br/>(EC_MAX_DATA_SIZE=1486)"] I --> J["分配 index + 记录时间戳"] J --> K["ec_device_send()<br/>master/device.c"] K --> L["dev_queue_xmit() / poll()"] L --> M["网卡硬件发送"]

ecrt_master_receive() 完整调用路径

位置: master/master.c:3106

flowchart TD A["ecrt_master_receive(master)"] --> B["遍历 dev_idx: MAIN → BACKUP"] B --> C["receive_cb → ec_master_internal_receive_cb"] C --> D["ec_master_receive_datagrams()<br/>master/master.c:1325"] D --> E["验证 EtherCAT 帧头<br/>(EtherType=0x88A4)"] E --> F["遍历帧中的 Datagram"] F --> G["通过 index 匹配已发送队列"] G --> H["拷贝数据到 datagram->data"] H --> I["更新 working_counter"] I --> J["更新 jiffies_received"] J --> K["将 datagram 移至完成"] K --> F

Domain 过程数据内存布局

ecrt_master_activate() 激活时为每个 Domain 分配连续的逻辑地址空间:

步骤 函数 说明
1 ec_domain_finish() 为 Domain 中每个 FMMU 计算逻辑地址偏移
2 domain_offset += domain->data_size 多个 Domain 的逻辑地址空间连续递增
3 分配 domain->data 分配物理连续内存(用于 DMA)
4 mmap 到用户空间 通过 ecrt_domain_data() 获取用户可访问指针
posted @ 2026-04-14 21:55  沐多  阅读(233)  评论(0)    收藏  举报