IgH EtherCAT主站Datagram、PDO及Mailbox模块如何详细介绍?
摘要:目录一、Datagram 模块概览Datagram 是什么?Datagram 类型一览技术详情ec_datagram 结构体字段Datagram 状态机Datagram 生命周期Datagram 在内核与用户空间的传递深入源码ec_data
目录一、Datagram 模块概览Datagram 是什么?Datagram 类型一览技术详情ec_datagram 结构体字段Datagram 状态机Datagram 生命周期Datagram 在内核与用户空间的传递深入源码ec_datagram_init()ec_datagram_prealloc()Datagram 构造函数超时与重试机制外部数据报环形缓冲ec_mbox_data_t — 邮箱响应数据二、PDO 模块概览PDO 是什么?PDO 层次结构技术详情核心数据结构ec_pdo_entry_t — PDO 条目ec_pdo_t — PDO 描述ec_pdo_list_t — PDO 列表PDO 映射表构建流程PDO 总大小计算深入源码PDO 核心函数ec_pdo_init() / ec_pdo_clear()ec_pdo_init_copy()ec_pdo_add_entry()ec_pdo_equal_entries()ec_pdo_copy_entries()PDO 列表函数ec_pdo_list_add_pdo_copy()ec_pdo_list_copy()ec_pdo_list_equal()ec_pdo_list_total_size()三、Mailbox 模块概览邮箱通信原理邮箱通信时序技术详情邮箱头格式邮箱协议类型邮箱操作流程深入源码ec_slave_mbox_prepare_send()ec_slave_mbox_prepare_check()ec_slave_mbox_check()ec_slave_mbox_prepare_fetch()ec_slave_mbox_fetch()邮箱协议复用机制
一、Datagram 模块
3.5 — datagram.c / datagram.h — 数据报管理
概览
Datagram 是什么?
Datagram (数据报) 是 EtherCAT 通信的基本传输单元。每个 Datagram 包含一个命令 (Command)、地址、数据和 Working Counter。多个 Datagram 可串联在一个以太网帧中发送。
核心特性
寻址模式: 支持自动递增 (AP)、配置地址 (FP)、广播 (B)、逻辑 (L) 四种寻址
状态机: INIT → QUEUED → SENT → RECEIVED/TIMED_OUT/ERROR
大小限制: 单个 Datagram 数据最大 EC_MAX_DATA_SIZE = 1486 字节
WKC 校验: 每个 Datagram 独立的 Working Counter 验证通信完整性
Datagram 类型一览
代码
类型名
说明
0x01
APRD
Auto Increment Physical Read
0x02
APWR
Auto Increment Physical Write
0x03
APRW
Auto Increment Physical ReadWrite
0x04
FPRD
Configured Address Physical Read
0x05
FPWR
Configured Address Physical Write
0x06
FPRW
Configured Address Physical ReadWrite
0x07
BRD
Broadcast Read
0x08
BWR
Broadcast Write
0x09
BRW
Broadcast ReadWrite
0x0A
LRD
Logical Read
0x0B
LWR
Logical Write
0x0C
LRW
Logical ReadWrite
0x0D
ARMW
Auto Increment Read Multiple Write
0x0E
FRMW
Configured Address Read Multiple Write
技术详情
ec_datagram 结构体字段
字段
类型
说明
queue
struct list_head
主站发送队列节点
sent
struct list_head
已发送队列节点
device_index
ec_device_index_t
发送设备索引(主/备)
type
ec_datagram_type_t
Datagram 类型
address[4]
uint8_t
目标地址(4 字节)
data
uint8_t *
数据缓冲区指针
data_origin
ec_origin_t
数据来源(内部/外部)
mem_size
size_t
数据缓冲区总大小
data_size
size_t
有效数据大小
index
uint8_t
Datagram 索引(主站自动分配,用于匹配响应)
working_counter
uint16_t
Working Counter 值
state
ec_datagram_state_t
Datagram 状态
jiffies_sent
unsigned long
发送时间(用于超时检测)
jiffies_received
unsigned long
接收时间
skip_count
unsigned int
未收到时重入队次数
name[20]
char
Datagram 描述名(调试用)
Datagram 状态机
stateDiagram-v2
[*] --> INIT: ec_datagram_init()
INIT --> QUEUED: ec_master_queue_datagram()
QUEUED --> SENT: ec_master_send_datagrams()
SENT --> RECEIVED: 匹配到响应帧
SENT --> TIMED_OUT: 超时 (EC_IO_TIMEOUT)
SENT --> ERROR: 发送/接收错误
RECEIVED --> QUEUED: 重新入队
TIMED_OUT --> QUEUED: 重试
ERROR --> INIT: 重置
Datagram 生命周期
flowchart TD
A[初始化 ec_datagram_init] --> B[配置: ec_datagram_lrd/lrw/...]
B --> C[入队: ec_master_queue_datagram]
C --> D[发送: ec_master_send_datagrams]
D --> E{收到响应?}
E -->|是| F[更新 data + WKC]
E -->|否| G{超时?}
G -->|否| H[重新入队 skip_count++]
H --> D
G -->|是| I[标记 TIMED_OUT]
F --> J[状态 → RECEIVED]
I --> K[FSM 重试或报错]
Datagram 在内核与用户空间的传递
sequenceDiagram
participant App as 用户应用
participant Lib as libec (用户空间)
participant Cdev as 字符设备 (内核)
participant Master as Master (内核)
participant NIC as 网卡驱动
App->>Lib: ecrt_master_send()
Lib->>Cdev: ioctl(EC_IOCTL_SEND)
Cdev->>Master: app_send_cb()
Master->>Master: ec_master_send_datagrams()
Master->>NIC: ec_device_send()
NIC->>NIC: DMA 发送帧
NIC->>Master: 中断/Poll 回调
Master->>Master: ec_master_receive_datagrams()
Master-->>Cdev: 完成
Cdev-->>Lib: ioctl 返回
Lib-->>App: 返回
深入源码
ec_datagram_init()
初始化 Datagram:清零所有字段,初始化链表节点,设置 state 为 EC_DATAGRAM_INIT,data_size 为 0。
ec_datagram_prealloc()
预分配数据缓冲区。若 mem_size < size,则重新分配。对于小数据量 (≤EC_MAX_DATA_SIZE) 使用内部缓冲区,大数据量使用外部缓冲区 (data_origin = EC_ORIG_EXTERNAL)。
Datagram 构造函数
每种 Datagram 类型有对应的构造函数,设置 type 和 address 字段:
函数
地址设置
ec_datagram_aprd(ap, offset, size)
address[0..1]=AP, address[2..3]=offset
ec_datagram_fprd(slave_addr, offset, size)
address[0..1]=slave_addr, address[2..3]=offset
ec_datagram_brd(offset, size)
address[0..1]=0x0000, address[2..3]=offset
ec_datagram_lrd(logical_addr, size)
address[0..3]=logical_addr
ec_datagram_frmw(slave_addr, offset, size)
address[0..1]=slave_addr, address[2..3]=offset
超时与重试机制
Datagram 发送后进入 sent 队列。每次 ec_master_send_datagrams() 调用时,检查已发送队列:
若 jiffies - jiffies_sent > EC_IO_TIMEOUT (1000μs),标记为 TIMED_OUT
若 skip_count 超过阈值,输出统计信息
未收到且未超时的 Datagram 保持 SENT 状态
外部数据报环形缓冲
Master 维护 ext_datagram_ring[EC_EXT_RING_SIZE=32],用于 FSM 状态机的 Datagram 需求。环形缓冲避免频繁分配/释放:
ext_ring_idx_fsm: FSM 侧索引(分配)
ext_ring_idx_rt: RT 侧索引(回收)
通过 injection_seq_fsm / injection_seq_rt 序列号协调并发
ec_mbox_data_t — 邮箱响应数据
字段
类型
说明
data
uint8_t *
邮箱响应数据缓冲区
data_size
size_t
缓冲区总大小
payload_size
size_t
有效载荷大小
每个从站为不同邮箱协议(CoE/FoE/SoE/EoE/VoE)维护独立的 ec_mbox_data_t,用于缓存接收到的邮箱响应。
二、PDO 模块
3.6 — pdo.c / pdo_entry.c / pdo_list.c — 过程数据对象
概览
PDO 是什么?
PDO (Process Data Object, 过程数据对象) 是 EtherCAT 中定义从站 I/O 数据格式的机制。每个 PDO 包含一个索引号和多个 PDO Entry (条目),每个 Entry 描述一个具体的数据字段(如位置反馈、控制字等)及其位宽。
PDO 的两种角色
TxPDO (0x1A00–0x1BFF): 从站→主站方向(输入),如编码器位置、状态字
RxPDO (0x1600–0x17FF): 主站→从站方向(输出),如控制字、目标位置
来源: PDO 映射在 EEPROM (SII) 中定义,也可通过 CoE (SDO) 动态重配置
PDO 层次结构
flowchart TD
A[Sync Manager] --> B[TxPDO List / RxPDO List]
B --> C1[PDO 0x1A00]
B --> C2[PDO 01A01]
C1 --> D1[Entry: 控制字 0x6040:00 16bit]
C1 --> D2[Entry: 目标位置 0x607A:00 32bit]
C1 --> D3[Entry: 操作模式 0x6060:00 8bit]
C2 --> D4[Entry: 扭矩限制 0x6072:00 16bit]
C2 --> D5[填充 0x0000:00 8bit]
技术详情
核心数据结构
ec_pdo_entry_t — PDO 条目
字段
类型
说明
list
struct list_head
链表节点
index
uint16_t
PDO 条目索引 (如 0x6040)
subindex
uint8_t
PDO 条目子索引 (如 0x00)
name
char *
条目名称(从 SII 字符串获取)
bit_length
uint8_t
位宽度(如 16、32)
ec_pdo_t — PDO 描述
字段
类型
说明
list
struct list_head
链表节点
index
uint16_t
PDO 索引 (如 0x1A00)
sync_index
int8_t
所属 Sync Manager 索引
name
char *
PDO 名称
entries
struct list_head
PDO 条目链表
ec_pdo_list_t — PDO 列表
字段
类型
说明
list
struct list_head
PDO 链表(包含多个 ec_pdo_t)
PDO 映射表构建流程
flowchart TD
A[从站扫描 fsm_slave_scan] --> B[读取 EEPROM SII]
B --> C[ec_slave_fetch_sii_pdos]
C --> D{Category Type?}
D -->|0x0032 TxPDO| E[解析 TxPDO 映射]
D -->|0x0033 RxPDO| F[解析 RxPDO 映射]
E --> G[创建 ec_pdo_t]
F --> G
G --> H[遍历 PDO 条目]
H --> I[创建 ec_pdo_entry_t]
I --> J[设置 index/subindex/bit_length]
J --> K[添加到 pdo.entries 链表]
K --> L[添加到 slave.sii.pdos]
M[应用程序配置] --> N[ecrt_slave_config_pdos]
N --> O[设置 Sync Manager PDO 分配]
O --> P[配置 PDO 映射]
P --> Q[ec_pdo_list_copy 覆盖默认映射]
PDO 总大小计算
ec_pdo_list_total_size() 遍历所有 PDO 的所有 Entry,累加 bit_length,向上取整到字节。这个值决定了 FMMU 的 data_size,进而影响 Domain 的逻辑地址空间分配。
⚠ 位级映射
当 PDO 总位宽不是 8 的整数倍时,需要填充 (Padding) Entry (index=0x0000, subindex=0x00)。IgH 通过 ec_pdo_add_entry() 支持添加填充条目。
深入源码
PDO 核心函数
ec_pdo_init() / ec_pdo_clear()
初始化/清理 PDO:设置默认值,释放名称字符串和所有 Entry。
ec_pdo_init_copy()
深拷贝 PDO:复制索引、名称和所有 Entry(递归调用 ec_pdo_entry_init_copy)。
ec_pdo_add_entry()
向 PDO 添加一个条目:分配 ec_pdo_entry_t,设置 index/subindex/bit_length,添加到 entries 链表尾部。
ec_pdo_equal_entries()
比较两个 PDO 的 Entry 列表是否一致(数量相同,每个 Entry 的 index/subindex/bit_length 相同)。用于判断从站当前 PDO 映射是否与配置匹配。
ec_pdo_copy_entries()
深拷贝 Entry 列表:先清空目标 PDO 的所有 Entry,再逐个拷贝源 PDO 的 Entry。
PDO 列表函数
ec_pdo_list_add_pdo_copy()
向 PDO 列表添加一个 PDO 的深拷贝。用于应用程序配置 PDO 映射时复制默认映射。
ec_pdo_list_copy()
整体拷贝 PDO 列表:清空目标列表,逐个深拷贝源列表的 PDO。
ec_pdo_list_equal()
比较两个 PDO 列表:遍历所有 PDO,调用 ec_pdo_equal_entries() 逐一比较。用于从站配置时判断是否需要重新配置 PDO 映射。
ec_pdo_list_total_size()
计算列表中所有 PDO 的总字节数:
unsigned int bit_size = 0;
list_for_each_entry(pdo, &pl-;>list, list) {
list_for_each_entry(entry, &pdo-;>entries, list) {
bit_size += entry->bit_length;
}
}
return (bit_size + 7) / 8; // 向上取整到字节
三、Mailbox 模块
3.7 — mailbox.c / mailbox.h — 邮箱通信
概览
邮箱通信原理
邮箱 (Mailbox) 是 EtherCAT 中主站与从站之间非周期性通信的机制,用于传输配置数据、诊断信息等。邮箱通信基于 Sync Manager 实现,使用双缓冲区和握手机制确保数据可靠传输。
邮箱特性
双缓冲区: 每个 Sync Manager 有两个缓冲区,交替使用
握手机制: 写入方设置 Mailbox Status Flag,读取方清除标志
协议复用: 同一邮箱通道传输多种协议 (CoE/EoE/FoE/SoE/VoE)
固定大小: 邮箱大小在 EEPROM 中定义 (通常 128–512 字节)
邮箱通信时序
sequenceDiagram
participant Master as 主站
participant SM as Sync Manager (邮箱)
participant Slave as 从站
Note over Master,Slave: 主站 → 从站 (写邮箱)
Master->>SM: FPWR 写入数据 + 设置写入标志
SM->>Slave: 从站检测到新数据
Slave->>SM: 读取数据 + 清除写入标志
Note over Master,Slave: 从站 → 主站 (读邮箱)
Slave->>SM: 写入响应数据 + 设置写入标志
SM-->>Master: 主站轮询检查 (BRD/FPRD)
Master->>SM: FPRD 读取响应 + 清除标志
技术详情
邮箱头格式
邮箱数据前 6 字节 (EC_MBOX_HEADER_SIZE = 6) 为邮箱头:
偏移
长度
字段
说明
0
2
Length
邮箱数据长度(不含头部)
2
1
Address
站地址
3
1
Channel/Priority
通道号 + 优先级
4
1
Type
邮箱协议类型
5
1
Counter/Status
计数器 / 状态标志
邮箱协议类型
值
名称
说明
使用场景
0x01
AOE
ADS over EtherCAT
Beckhoff ADS 通信
0x02
EOE
Ethernet over EtherCAT
虚拟网络隧道
0x03
COE
CANopen over EtherCAT
SDO/PDO/Emergency
0x04
FOE
File Access over EtherCAT
固件升级
0x05
SOE
Servo over EtherCAT
伺服驱动 IDN 访问
0x0F
VOE
Vendor-specific
厂商自定义协议
邮箱操作流程
flowchart TD
A[ec_slave_mbox_prepare_send] --> B[构造 FPWR Datagram]
B --> C[写入从站接收邮箱]
C --> D[ec_slave_mbox_prepare_check]
D --> E[构造 FPRD Datagram]
E --> F[ec_slave_mbox_check]
F --> G{邮箱有数据?}
G -->|否| H[等待/重试]
H --> D
G -->|是| I[ec_slave_mbox_prepare_fetch]
I --> J[构造 FPRD Datagram]
J --> K[ec_slave_mbox_fetch]
K --> L[解析邮箱头 + 返回载荷]
L --> M{Type 匹配?}
M -->|是| N[返回数据]
M -->|否| O[检查错误码]
O --> P{邮箱错误?}
P -->|是| Q[返回错误]
P -->|否| R[重新检查]
深入源码
ec_slave_mbox_prepare_send()
位置: master/mailbox.c:51–93
准备发送邮箱数据:
预分配 Datagram (ec_datagram_fpwr),目标为从站接收邮箱偏移 (configured_rx_mailbox_offset)
计算总大小: EC_MBOX_HEADER_SIZE + size
填充邮箱头: Length、Address(站地址)、Channel、Type(协议类型)
返回指向载荷区域的指针,供调用者填入协议数据
ec_slave_mbox_prepare_check()
位置: master/mailbox.c:103–113
准备检查邮箱状态:构造 ec_datagram_fprd 读取从站发送邮箱的 Sync Manager 状态 (偏移 +4 的第 5 字节)。此操作是轮询式的——FSM 反复调用直到从站将数据写入邮箱。
ec_slave_mbox_check()
位置: master/mailbox.c:122–125
检查邮箱是否有数据:读取 datagram->data[5] 的第 5 位 (Mailbox Status Flag)。若为 1 表示从站已写入数据到发送邮箱。
ec_slave_mbox_prepare_fetch()
位置: master/mailbox.c:134–146
准备读取邮箱数据:构造 ec_datagram_fprd,读取从站发送邮箱完整内容(大小为 configured_tx_mailbox_size)。
ec_slave_mbox_fetch()
位置: master/mailbox.c:172–229
处理接收到的邮箱数据:
读取邮箱头中的 Length 字段
验证邮箱状态 (Counter 字段)
检测邮箱错误 (bit 0 of Counter 为错误标志)
返回指向载荷数据的指针和大小
此函数由各协议 FSM 调用,FSM 负责解析具体的协议内容。
邮箱协议复用机制
同一个从站的邮箱通道被多种协议共享。IgH 的处理方式:
每个从站的 ec_slave 中为每种协议维护独立的 ec_mbox_data_t 缓冲区
ec_mbox_prot_data_prealloc() 为指定协议预分配缓冲区
当收到邮箱响应时,根据 Type 字段分发到对应协议的缓冲区
valid_mbox_data 标志指示有未处理的邮箱数据
mbox_sem 信号量保护邮箱访问的互斥
