一、分布式时钟 (Distributed Clock, DC)
1.4 — DC 全网精确时间同步原理、64 位系统时间、漂移补偿、SYNC 信号(基于 N32H7x5EC)
概览
为什么需要分布式时钟?
在工业自动化中,多个从站需要在完全相同的时刻采集输入信号或更新输出信号。即使微秒级的时间差异也会导致运动控制中的位置误差或测量数据不一致。EtherCAT 分布式时钟 (Distributed Clock, DC) 机制实现全网纳秒级精确时间同步。
N32H7x5EC DC 核心特性
系统时间: 64 位,从 2000-01-01 00:00 开始,基本单位 1 ns
内部更新速率: 100 MHz
同步抖动: 约 12 ns
同步信号: SYNC0 + SYNC1,可配置周期和脉冲长度
锁存信号: LATCH0/LATCH1,采样率 100 MHz,抖动约 11 ns
DC 同步时序

图:DC 传播延迟测量与 SYNC0/SYNC1 信号时序关系
技术详情
64 位系统时间
EtherCAT DC 使用 64 位纳秒计数器表示系统时间,从 2000-01-01 00:00 开始计时,基本单位为 1 ns。64 位时间可表示约 584 年的范围,无需处理溢出问题。每个具有 DC 功能的从站都维护独立的本地时钟。
时钟同步关键概念
| 概念 |
说明 |
| 系统时间 |
从 2000-01-01 00:00 开始,64 位,基本单位 1 ns |
| 参考时钟 |
主站后第一个具有 DC 功能的 ESC |
| 本地时钟 |
每个 DC 从站独立运行的自由时钟 |
| 偏移 (Offset) |
本地时钟与参考时钟的初始差异 |
| 漂移 (Drift) |
时钟源的微小频率偏差,需持续补偿 |
公式:tLocal copy of System Time = tLocal time + tOffset
DC 寄存器映射表
| 地址 |
寄存器名 |
大小 |
说明 |
| 0x0900–0x0903 |
ESC_DCRXTIMP0 |
4 B |
DC 接收时间端口 0 |
| 0x0904–0x0907 |
ESC_DCRXTIMP1 |
4 B |
DC 接收时间端口 1 |
| 0x0910–0x0917 |
ESC_DCSYSTIM |
8 B |
DC 系统时间 (64 位纳秒) |
| 0x0918–0x091F |
DC 本地时间 |
8 B |
本地时间 = 系统时间 - 偏移 |
| 0x0920–0x0927 |
ESC_DCSYSTIMOST |
8 B |
DC 系统时间偏移 |
| 0x0928–0x092B |
ESC_DCSYSTIMDLY |
4 B |
DC 系统时间延迟 |
| 0x092C–0x092F |
ESC_DCSYSTIMDIFF |
4 B |
DC 系统时间差 |
| 0x0930–0x0933 |
ESC_DCSCSAT |
4 B |
DC 速度计数器启动值 |
| 0x0934–0x0937 |
ESC_DCSCDIFF |
4 B |
DC 速度计数器差 |
| 0x0938 |
ESC_DCSTDFD |
1 B |
DC 系统时间差滤波深度 |
| 0x0939 |
ESC_DCSCFD |
1 B |
DC 速度计数器滤波深度 |
| 0x0980 |
ESC_DCCUCTRL |
1 B |
DC 循环单元控制 |
| 0x0981 |
ESC_DCACT |
1 B |
DC 激活 (SYNC0/SYNC1 使能) |
| 0x0982–0x0983 |
ESC_DCSSPLEN |
2 B |
DC 脉冲长度 |
| 0x0984 |
ESC_DCACTSTS |
1 B |
DC 激活状态 |
| 0x098E |
ESC_DCSYNC0STS |
1 B |
DC SYNC0 状态 |
| 0x098F |
ESC_DCSYNC1STS |
1 B |
DC SYNC1 状态 |
| 0x0990–0x0997 |
ESC_DCSTCOPE |
8 B |
DC 起始时间循环操作 |
| 0x0998–0x099F |
ESC_DCNSYNC1P |
8 B |
DC Next SYNC1 脉冲 |
| 0x09A0–0x09A3 |
ESC_DCSYNC0CT |
4 B |
DC SYNC0 周期时间 |
| 0x09A4–0x09A7 |
ESC_DCSYNC1CT |
4 B |
DC SYNC1 周期时间 |
| 0x09AE |
ESC_DCLATCH0CTRL |
1 B |
DC Latch0 控制 |
| 0x09AF |
ESC_DCLATCH1CTRL |
1 B |
DC Latch1 控制 |
| 0x09B0–0x09B7 |
— |
8 B |
DC Latch0 时间正边沿 |
| 0x09B8–0x09BF |
— |
8 B |
DC Latch0 时间负边沿 |
| 0x09C0–0x09C7 |
— |
8 B |
DC Latch1 时间正边沿 |
| 0x09C8–0x09CF |
— |
8 B |
DC Latch1 时间负边沿 |
| 0x09F0–0x09F7 |
ESC_DCECATBCET |
8 B |
ECAT 缓冲区更改事件时间 |
| 0x09F8–0x09FF |
ESC_DCPDIBSET |
8 B |
PDI 缓冲区起始事件时间 |
| 0x0A00–0x0A07 |
ESC_DCPDIBCET |
8 B |
PDI 缓冲区更改事件时间 |
DC 同步流程
flowchart TD
A["ec_master_calc_dc()<br/>DC 同步入口"] --> B["ec_master_find_dc_ref_clock()"]
B --> C["ec_master_calc_topology()"]
C --> D["ec_master_calc_transmission_delays()"]
D --> E["ecrt_master_activate()"]
E --> F["Slave Config FSM<br/>DC 配置子状态机"]
B -.->|"优先级 1"| B1["应用指定的参考时钟<br/>slave_config->dc_assign_activate"]
B -.->|"优先级 2"| B2["第一个有 DC 能力的从站<br/>slave->has_dc"]
C -.->|"遍历从站"| C1["计算拓扑延迟<br/>parent → child 延迟累加"]
D -.->|"BRD 广播"| D1["测量每段传播延迟<br/>0x0910 系统时间锁存"]
F --> F1["dc_clear_assign<br/>清除旧分配"]
F1 --> F2["dc_cycle<br/>设置 SYNC 周期"]
F2 --> F3["dc_sync_check<br/>等待时钟同步"]
F3 --> F4["dc_start<br/>设置启动时间"]
F4 --> F5["dc_assign<br/>激活 DC"]
style A fill:#ebf8ff,stroke:#90cdf4
style B fill:#fefcbf,stroke:#f6e05e
style C fill:#fefcbf,stroke:#f6e05e
style D fill:#fefcbf,stroke:#f6e05e
style F fill:#faf5ff,stroke:#d6bcfa
参考时钟选择流程
flowchart TD
START["ec_master_find_dc_ref_clock()"] --> CHECK_APP{"应用是否指定了<br/>DC 参考时钟?"}
CHECK_APP -->|"是"| APP_REF["使用应用指定的从站<br/>作为参考时钟"]
CHECK_APP -->|"否"| SCAN["遍历所有从站"]
SCAN --> HAS_DC{"找到第一个<br/>has_dc=1 的从站?"}
HAS_DC -->|"是"| FIRST_DC["选择第一个有 DC 能力的从站<br/>作为参考时钟"]
HAS_DC -->|"否"| NO_DC["无 DC 参考时钟<br/>跳过 DC 同步"]
APP_REF --> INIT_DATAGRAM["初始化三个 DC 数据报"]
FIRST_DC --> INIT_DATAGRAM
INIT_DATAGRAM --> D1["ref_sync_datagram<br/>FPWR 0x0910<br/>同步参考时钟到主站"]
INIT_DATAGRAM --> D2["sync_datagram<br/>FPRD 0x0910<br/>读取参考时钟用于漂移补偿"]
INIT_DATAGRAM --> D3["sync64_datagram<br/>ARMW/FRMW 0x0910<br/>64 位系统时间读写"]
style START fill:#ebf8ff,stroke:#90cdf4
style APP_REF fill:#f0fff4,stroke:#9ae6b4
style FIRST_DC fill:#f0fff4,stroke:#9ae6b4
style NO_DC fill:#fff5f5,stroke:#feb2b2
style INIT_DATAGRAM fill:#faf5ff,stroke:#d6bcfa
同步过程四步骤
DC 同步核心步骤
步骤 1 — 传播延迟测量:主站广播写操作,从站锁存本地时间 (0x0900/0x0904)
步骤 2 — 偏移补偿:将差异写入系统时间偏移寄存器 (0x0920)
步骤 3 — 重置时间控制环路:写入速度计数器起始值 (0x0930)
步骤 4 — 漂移补偿:周期性发送 ARMW/FRMW 命令,持续校正时钟
传播延迟测量
sequenceDiagram
participant M as 主站 Master
participant S0 as 从站 0<br/>(DC)
participant S1 as 从站 1
participant S2 as 从站 2<br/>(DC)
Note over M,S2: 步骤 1:BWR 广播,从站锁存到达时间
M->>S0: BWR 0x0910 (触发时间锁存)
Note right of S0: 锁存 t₀ 到 0x0900
S0->>S1: 转发帧
Note right of S1: 锁存 t₁ 到 0x0900
S1->>S2: 转发帧
Note right of S2: 锁存 t₂ 到 0x0900
S2-->>S1: 返回帧
S1-->>S0: 返回帧
S0-->>M: 返回帧
Note over M,S2: 步骤 2:FPRD 读取各从站锁存时间
M->>S0: FPRD 0x0900 ← t₀
M->>S1: FPRD 0x0900 ← t₁
M->>S2: FPRD 0x0900 ← t₂
Note over M,S2: 步骤 3:计算并写入传输延迟
Note right of M: delay₀ = t₀ (环路/2)<br/>delay₁ = t₁ - t₀<br/>delay₂ = t₂ - t₀
M->>S0: FPWR 0x0928 ← delay₀
M->>S1: FPWR 0x0928 ← delay₁
M->>S2: FPWR 0x0928 ← delay₂
DC 配置子状态机详解
| 状态 |
寄存器操作 |
说明 |
dc_clear_assign |
写 0x0981 = 0 |
清除旧的 DC 激活,防止残留配置干扰 |
dc_cycle |
写 0x09A0 (Sync0 周期) 写 0x09A4 (Sync1 周期) |
设置 SYNC0/SYNC1 中断的周期时间 |
dc_sync_check |
读 0x092C |
循环读取系统时间差,等待 < 10μs(最多等 5s) |
dc_start |
写 0x0990 |
计算周期启动时间 = app_time + 100ms,写入起始时间 |
dc_assign |
写 0x0981 |
写入 DC 激活值 (DCACT),启用 SYNC0/SYNC1 生成 |
从站时钟类型
| 类型 |
说明 |
DC 支持 |
| Bus Shift Register |
无 DC 能力,仅转发数据帧 |
不支持 |
| Single-Cut-Through |
有 DC,但不补偿传播延迟 |
部分支持 |
| Double-Cut-Through |
有 DC,补偿传播延迟 (最精确) |
完全支持 |
同步信号 (SYNC0 / SYNC1)
SYNC0 是基础同步信号,支持 4 种模式:
| 模式 |
说明 |
| 循环生成 |
按周期时间 (0x09A0) 持续生成脉冲 |
| 单次触发 |
仅生成一次脉冲 |
| 循环应答 |
每次脉冲需要确认后才生成下一个 |
| 单次应答 |
单次脉冲带应答 |
SYNC1 依赖 SYNC0,可配置延迟。内部更新速率 100 MHz,同步抖动约 12 ns。脉冲长度由 0x0982 寄存器设置(>0 为周期模式)。
锁存信号 (LATCH0 / LATCH1)
LATCH0/LATCH1 支持上升沿和下降沿时间戳,采样率 100 MHz,抖动约 11 ns。
| 模式 |
说明 |
| 单事件模式 |
仅锁存第一个事件的时间戳 |
| 连续模式 |
持续锁存每个事件的时间戳 |
通信模式
| 模式 |
描述 |
| 自由运行 |
通信与应用独立运行 |
| 输出事件同步 |
应用与输出事件同步 |
| 同步信号同步 |
应用与 DC 同步信号同步 |
漂移补偿流程
每个通信周期,主站通过三个专用数据报持续校正全网时钟漂移:
| 数据报 |
类型 |
地址 |
方向 |
用途 |
ref_sync_datagram |
FPWR |
0x0910 |
主站 → 参考从站 |
将主站时间写入参考时钟从站 |
sync_datagram |
FPRD |
0x0910 |
参考从站 → 主站 |
读取参考时钟当前时间 (64 位) |
sync64_datagram |
ARMW/FRMW |
0x0910 |
参考从站 → 其他从站 |
读取参考时钟并传播到其他从站 |
sequenceDiagram
participant M as 主站 Master
participant Dev as ec_device (网卡)
participant Ref as 参考时钟从站
participant S1 as 从站 1
participant S2 as 从站 2
Note over M,S2: 每个周期:ecrt_master_send() 触发
rect rgb(235, 248, 255)
Note over M,Ref: 步骤 1:同步参考时钟 (ref_sync_datagram)
M->>Dev: FPWR 0x0910 ← master_app_time (64 位)
Dev->>Ref: 帧发送 (DMA)
Note right of Ref: 参考时钟被同步到主站时间
end
rect rgb(240, 255, 244)
Note over M,Ref: 步骤 2:读取参考时间 (sync_datagram)
M->>Dev: FPRD 0x0910
Dev->>Ref: 读取系统时间请求
Ref-->>Dev: 返回当前 64 位系统时间 T_ref
Dev-->>M: 接收 T_ref
Note right of M: 计算 app_time - T_ref<br/>得到漂移补偿偏移 Δ
end
rect rgb(250, 245, 255)
Note over M,S2: 步骤 3:漂移补偿 (写入各从站偏移)
M->>Dev: FPWR 0x0920 ← Δ₁ (S1 偏移)
Dev->>S1: 校正 S1 系统时间偏移
M->>Dev: FPWR 0x0920 ← Δ₂ (S2 偏移)
Dev->>S2: 校正 S2 系统时间偏移
end
Note over M,S2: 纳秒级漂移被持续校正 (< 1μs 偏差)
漂移补偿算法流程
flowchart TD
START["周期开始<br/>ecrt_master_send()"] --> REF_SYNC["ref_sync_datagram<br/>FPWR 0x0910 → 参考从站<br/>写入主站 app_time (64 位)"]
REF_SYNC --> SEND["发送所有数据报<br/>ec_master_send_datagrams()"]
SEND --> RECV["接收返回帧<br/>ec_master_receive_datagrams()"]
RECV --> READ_REF["处理 sync_datagram<br/>读取参考时钟 T_ref (64 位)"]
READ_REF --> CALC["计算漂移<br/>Δ = app_time - T_ref<br/>- 传播延迟补偿"]
CALC --> CHECK{"|Δ| < 阈值?"}
CHECK -->|"是"| APPLY["写入各从站 0x0920<br/>系统时间偏移寄存器"]
CHECK -->|"否"| LIM["限幅处理<br/>防止过冲"]
LIM --> APPLY
APPLY --> NEXT["下一周期继续补偿"]
NEXT --> START
style START fill:#ebf8ff,stroke:#90cdf4
style CALC fill:#fefcbf,stroke:#f6e05e
style APPLY fill:#f0fff4,stroke:#9ae6b4
style LIM fill:#fff5f5,stroke:#feb2b2
深入源码
源码:master/sync.h — ec_sync 结构体
master/sync.h — ec_sync_t
typedef struct {
ec_slave_t *slave; // 所属从站
uint16_t physical_start_address; // 物理起始地址
uint16_t default_length; // 默认数据长度
uint8_t control_register; // 控制寄存器值
uint8_t enable; // 使能位
ec_pdo_list_t pdos; // PDO 分配列表
} ec_sync_t;
源码:master/fsm_slave_config.c — DC 配置状态机
DC 配置是 Slave Config FSM 的一部分,发生在 PREOP → SAFEOP 转换过程中。相关常量:
master/fsm_slave_config.c — DC 相关常量
#define EC_DC_MAX_SYNC_DIFF_NS 10000 // 最大允许时钟偏差: 10μs
#define EC_DC_SYNC_WAIT_MS 5000 // 等待时钟同步最长: 5秒
#define EC_DC_START_OFFSET 100000000ULL // 周期启动偏移: 100ms
DC 配置状态机流程
stateDiagram-v2
[*] --> dc_clear_assign: 进入 DC 配置
dc_clear_assign --> dc_cycle: 清除 DC 激活<br/>(写 0x0981 = 0)
dc_cycle --> dc_sync_check: 设置 SYNC0/SYNC1 周期<br/>(写 0x09A0, 0x09A4)<br/>设置脉冲长度 (0x0982)
dc_sync_check --> dc_start: 时间差 < 10μs<br/>读取 0x092C 确认
dc_sync_check --> dc_sync_check: 等待同步<br/>(循环读取 0x092C)
dc_start --> dc_assign: 设置启动时间<br/>(写 0x0990)
dc_assign --> safeop: 激活 DC<br/>(写 0x0981 DCACT)
note left of dc_clear_assign: 写入 0x0981\n清除旧的 DC 激活
note left of dc_cycle: 写入周期\nSYNC0 (0x09A0) 和 SYNC1 (0x09A4)
note left of dc_sync_check: 读取 0x092C 检查系统时间差\n等待时钟同步偏差 < 10μs
note left of dc_start: 写入 0x0990\n周期操作启动时间
note left of dc_assign: 写入 0x0981\nDC 激活 (DCACT)
DC 配置关键步骤
-
dc_clear_assign: 写入 0x0981 清除旧的 DC 激活
-
dc_cycle: 写入 0x09A0/0x09A4 设置 SYNC0/SYNC1 周期,写入 0x0982 设置脉冲长度
-
dc_sync_check: 循环读取 0x092C 等待时钟同步 (<10μs)
-
dc_start: 计算并写入 0x0990 周期操作启动时间
-
dc_assign: 写入 0x0981 激活 DC 功能 (DCACT 寄存器)
ec_master_calc_dc() 完整调用链
以下展示主站 DC 同步的完整内核调用链:
flowchart TD
subgraph "ec_master_calc_dc()"
A["ec_master_calc_dc()"] --> B["ec_master_find_dc_ref_clock()"]
B --> B1{"找到 DC 参考时钟?"}
B1 -->|"否"| NO_DC["返回,不执行 DC"]
B1 -->|"是"| C["ec_master_calc_topology()"]
C --> C1["遍历所有从站<br/>计算 parent → child 延迟"]
C1 --> D["ec_master_calc_transmission_delays()"]
D --> D1["测量各段传播延迟<br/>(0x0900 端口接收时间)"]
D1 --> D2["写入各从站 0x0928 延迟寄存器"]
end
subgraph "DC 数据报初始化"
D2 --> E["ec_datagram_fpwr()<br/>ref_sync_datagram<br/>地址: 0x0910 (64 位)"]
D2 --> F["ec_datagram_fprd()<br/>sync_datagram<br/>地址: 0x0910 (64 位)"]
D2 --> G["ec_datagram_armw()<br/>sync64_datagram<br/>地址: 0x0910 (64 位)"]
end
subgraph "运行时周期补偿"
E --> H["每个周期:<br/>ec_master_sync_slave_clocks()"]
H --> H1["ref_sync: FPWR 主站时间 → 参考从站 0x0910"]
H --> H2["sync: FPRD 读参考时间 0x0910 → 计算偏移"]
H --> H3["写入各从站 0x0920 系统时间偏移寄存器"]
end
style A fill:#ebf8ff,stroke:#90cdf4
style NO_DC fill:#fff5f5,stroke:#feb2b2
style E fill:#f0fff4,stroke:#9ae6b4
style F fill:#f0fff4,stroke:#9ae6b4
style G fill:#f0fff4,stroke:#9ae6b4
style H fill:#faf5ff,stroke:#d6bcfa
SYNC0/SYNC1 信号时序与从站采样
sequenceDiagram
participant M as 主站 Master
participant Ref as 参考时钟从站
participant S1 as 从站 1
participant S2 as 从站 2
Note over M,S2: 周期 T = 1ms (例如)
rect rgb(235, 248, 255)
Note over Ref: SYNC0 ↑ (T₀)
Note over S1: SYNC0 ↑ (T₀ + delay₁)
Note over S2: SYNC0 ↑ (T₀ + delay₂)
Note right of Ref: 所有从站在<br/>精确同一时刻<br/>触发数据采样<br/>(抖动 ~12ns)
end
M->>Ref: 发送 EtherCAT 帧
Ref->>S1: 转发
S1->>S2: 转发
Note over M,S2: 帧处理:输入数据被锁存
S2-->>S1: 返回帧
S1-->>Ref: 返回帧
Ref-->>M: 返回帧
rect rgb(240, 255, 244)
Note over Ref: SYNC0 ↑ (T₀ + T)
Note over S1: SYNC0 ↑ (T₀ + T + delay₁)
Note over S2: SYNC0 ↑ (T₀ + T + delay₂)
Note right of Ref: 输出数据在<br/>SYNC0 时刻更新
end
同步信号初始化步骤(编程指南)
N32H7x5EC 同步信号初始化流程
-
在 ESC 配置寄存器启用 DC SYNC 输出单元(bit2 = 1)
-
设置 SYNC/Latch PDI 配置寄存器(输出 SYNC0/1,配置驱动参数)
-
设置脉冲长度寄存器 0x0982(>0 为周期模式)
-
将同步单元分配给 ECAT 或 PDI
-
设置 SYNC0 周期时间 0x09A0 和 SYNC1 周期时间 0x09A4
-
设置周期操作启动时间 0x0990(应晚于当前时间)
-
设置 DC 激活寄存器 0x0981 启用 SYNC0/SYNC1 生成
SYNC0 vs SYNC1
SYNC0:主同步信号,触发所有从站的数据采样和输出更新。周期由 0x09A0 寄存器设置,内部更新速率 100 MHz,抖动约 12 ns。
SYNC1:辅助同步信号,依赖 SYNC0,可配置延迟。周期由 0x09A4 寄存器设置,通常为 SYNC0 周期的整数倍。
DC 中断方式
DC 同步信号中断有两种方式:
| 方式 |
抖动 |
说明 |
| 映射到 AL 事件请求寄存器 |
~40 ns |
合并为单个 IRQ_A (ECAT_OPB_IRQ) |
| 直接连到微控制器中断输入 |
~12 ns |
IRQ_B (ECAT_SYNC0_INT), IRQ_C (ECAT_SYNC1_INT),精度更高 |
二、AL 状态机 (Application Layer State Machine)
1.5 — EtherCAT 从站应用层状态机、状态转换条件与错误处理
概览
AL 状态机简介
每个 EtherCAT 从站具有一个应用层状态机 (AL State Machine),定义了从站的四种工作状态。主站通过向从站的 AL 状态寄存器 (0x0120) 写入目标状态来请求状态转换,从站完成内部初始化后自动转换到目标状态并确认。
完整 AL 状态机图
stateDiagram-v2
[*] --> Init: 从站上电
Init --> PreOp: 主站请求 0x02
PreOp --> Init: 主站请求 0x01 / 错误回退
PreOp --> SafeOp: 配置完成,主站请求 0x04
SafeOp --> PreOp: 主站请求 0x02 / 错误回退
SafeOp --> Op: 数据验证通过,主站请求 0x08
Op --> SafeOp: 主站请求 0x04 / 错误回退
Init --> Bootstrap: 主站请求 0x03
Bootstrap --> Init: 完成 / 超时
Op --> Error: 通信故障 / WKC 异常
SafeOp --> Error: 配置不匹配
PreOp --> Error: 邮箱初始化失败
Error --> Init: 主站请求 0x01 (错误恢复)
各状态能力对比
| 状态 |
代码 |
邮箱通信 |
过程数据输入 |
过程数据输出 |
说明 |
| Init |
0x01 |
不可用 |
不可用 |
不可用 |
上电初始状态,ESC 寄存器可访问 |
| Pre-Op |
0x02 |
可用 |
不可用 |
不可用 |
可配置从站参数 (SDO/PDO 映射) |
| Safe-Op |
0x04 |
可用 |
可读 |
安全值 |
输入数据有效,输出处于安全状态 |
| Op |
0x08 |
可用 |
可读 |
可写 |
正常运行,完整数据交换 |
技术详情
状态转换请求-确认机制
sequenceDiagram
participant M as 主站 Master
participant S as 从站 Slave
Note over M,S: 状态转换请求-确认流程
M->>S: 写 AL 状态请求 (0x0120)<br/>写入目标状态
Note over S: 从站开始内部初始化
loop 轮询检查 (超时 5s)
M->>S: 读 AL 状态 (0x0130)
S-->>M: 返回当前状态
end
Note over M,S: 状态匹配 → 转换成功
Note over M,S: ACK_ERR 标志 → 需要确认错误
M->>S: 写当前状态确认 (0x0120)<br/>清除 ACK_ERR 标志
ESC 相关寄存器
| 地址 |
名称 |
读/写 |
说明 |
| 0x0120 |
AL Control |
写 |
主站写入目标 AL 状态 |
| 0x0130 |
AL Status |
读 |
从站当前 AL 状态 (含 ACK_ERR 位) |
| 0x0134 |
AL Status Code |
读 |
状态转换错误码 (详细错误信息) |
常见 AL 状态码 (0x0134)
| 错误码 |
说明 |
常见原因 |
| 0x0000 |
无错误 |
正常状态 |
| 0x0001 |
无错误 (状态转换完成) |
转换成功 |
| 0x0011 |
无效的状态转换 |
请求了不允许的转换 |
| 0x0012 |
未知状态 |
请求了不支持的状态 |
| 0x0013 |
Bootstrap 不支持 |
从站不支持 Bootstrap 模式 |
| 0x0014 |
无有效输出数据 |
过程数据输出未配置 |
| 0x0015 |
无效的输出配置 |
PDO 映射与 SM 配置不匹配 |
| 0x001A |
无效的输入配置 |
输入 PDO 映射错误 |
| 0x001B |
无效的 Sync Manager 配置 |
SM 地址或长度配置错误 |
| 0x001C |
无有效的输入数据 |
输入过程数据无效 |
| 0x0020 |
从站需要重新扫描 |
从站配置变更 |
各状态转换要求
| 转换 |
前提条件 |
从站动作 |
| Init → Pre-Op |
ESC 基本通信可用 |
初始化邮箱 (SM0/SM1),加载 EEPROM 配置 |
| Pre-Op → Safe-Op |
SDO 配置完成,PDO 映射有效 |
初始化 FMMU,配置过程数据 SM,启动 DC (如支持) |
| Safe-Op → Op |
输入数据有效,WKC 正常 |
使能输出数据,开始正常数据交换 |
| 任意 → Error |
通信故障、WKC 异常、配置不匹配 |
设置 ACK_ERR 标志,停止输出 |
| Error → Init |
主站发送 Init 状态请求 |
完全复位,清除所有配置 |
深入源码
源码:master/fsm_change.h
master/fsm_change.h — ec_fsm_change 结构体
struct ec_fsm_change {
ec_slave_t *slave; // 目标从站
ec_datagram_t *datagram; // 使用的 datagram
unsigned int retries; // 超时重试次数
void (*state)(ec_fsm_change_t *, ec_datagram_t *); // 当前状态函数
ec_fsm_change_mode_t mode; // 模式: 完整转换 / 仅确认
ec_slave_state_t requested_state; // 请求的目标状态
ec_slave_state_t old_state; // 转换前的状态
unsigned long jiffies_start; // 转换计时起始
uint8_t take_time; // 是否记录发送时间戳
uint8_t spontaneous_change; // 检测到自发状态变化
};
State Change FSM 状态机 (9 个状态)
stateDiagram-v2
[*] --> start: 开始状态转换
state "发送状态请求" as start
state "验证发送" as check
state "检查当前状态" as status
state "处理错误码" as code
state "确认当前状态" as ack
state "验证确认" as check_ack
state "转换完成" as done
state "转换失败" as error
start --> check: 写入请求状态到 AL Control (0x0120)
check --> status: 验证 Datagram WKC 检查
check --> check: 超时重试
status --> done: 当前状态 = 请求状态(转换成功)
status --> code: 状态不匹配, 读取错误码
status --> error: 超时 (5秒)
code --> ack: 读取 AL Status Code (0x0134)
ack --> check_ack: 写入当前状态确认(清除 ACK_ERR)
check_ack --> done: ACK_ERR 已清除
check_ack --> error: 确认失败 / 超时
done --> [*]
error --> [*]
关键实现细节
FSM 实现要点
-
超时机制: 使用 jiffies_start 记录起始时间,状态转换超时 EC_AL_STATE_CHANGE_TIMEOUT 为 5 秒
-
重试机制: Datagram 发送失败时自动重试 EC_FSM_RETRIES 次
-
两种模式: EC_FSM_CHANGE_MODE_FULL 完整转换, EC_FSM_CHANGE_MODE_ACK_ONLY 仅确认
-
自发变化检测: spontaneous_change 标志检测从站主动上报的状态变化
-
ESC 寄存器: 写 0x0120 (AL Control), 读 0x0130 (AL Status), 读 0x0134 (AL Status Code)