CANopen TIME/SYNC 运行流程
@[toc]
1. 先把 TIME 和 SYNC 分开
TIME 和 SYNC 都属于 CANopen 的特殊功能通信对象,但它们解决的问题不同:
| 对象 | 核心问题 | 默认 CAN-ID | 数据长度 | 典型用途 |
|---|---|---|---|---|
SYNC |
“什么时候进入一个同步周期?” | 0x080 |
0 或 1 |
同步 TPDO/RPDO、周期采样、周期控制 |
TIME |
“网络当前时间是多少?” | 0x100 |
6 |
网络时间校准、时间戳、日志时间基准 |
一句话区分:
1 | SYNC 是节拍;TIME 是时钟。 |
SYNC 不携带实际过程数据,它只广播一个同步事件。同步 PDO 在这个事件前后按配置动作。TIME 也不携带过程数据,它只携带“当天毫秒数 + 自 1984-01-01 起的天数”。
2. 协议侧:SYNC 规定了什么
2.1 SYNC 的角色
SYNC 使用 producer/consumer 模型:
1 | flowchart LR |
网络中通常只有一个 SYNC producer,其他节点作为 consumer。producer 的任务是周期性发送 SYNC 帧;consumer 收到后把它当成同步边界。
2.2 SYNC 帧格式
SYNC 默认是一个空数据帧:
1 | CAN-ID = 0x080 |
如果启用同步计数器,则变成:
1 | CAN-ID = 0x080 |
计数器从 1 开始递增,到 0x1019 Synchronous counter overflow value 指定的最大值后回到 1。如果本地配置要求 DLC=0,却收到 DLC=1;或配置要求 DLC=1,却收到其他长度,consumer 应视为同步数据长度错误。
2.3 与 SYNC 直接相关的对象字典
| OD | 名称 | 对源码的影响 |
|---|---|---|
0x1005 |
COB-ID SYNC message | 决定 SYNC CAN-ID;bit30 决定本节点是否 producer |
0x1006 |
Communication cycle period | producer 的发送周期;consumer 的超时监控基准 |
0x1007 |
Synchronous window length | 同步窗口;窗口外同步 PDO 应被限制 |
0x1019 |
Synchronous counter overflow value | 为 0 时 SYNC 无数据;大于 1 时 SYNC 携带 1 字节 counter |
0x1006 = 0 等价于不启用周期 SYNC;0x1007 = 0 等价于关闭同步窗口约束;0x1019 = 0 等价于不使用 SYNC counter。
2.4 同步窗口的含义
1 | flowchart LR |
同步窗口是从 SYNC 边沿开始的一段时间。窗口内,同步 PDO 可以按配置处理;窗口外,源码用 syncIsOutsideWindow 记录状态,并通过 CO_SYNC_PASSED_WINDOW 通知上层“刚刚越过窗口”。
3. 协议侧:TIME 规定了什么
3.1 TIME 的角色
TIME 也是 producer/consumer 模型:
1 | flowchart LR |
TIME 解决的是“网络时间一致性”,不是“周期动作边界”。周期动作边界仍应看 SYNC。
3.2 TIME 帧格式
TIME 的数据长度固定为 6 字节:
1 | CAN-ID = 0x100 默认值 |
源码中的结构对应为:
1 | uint32_t ms; /* milliseconds after midnight */ |
3.3 与 TIME 直接相关的对象字典
| OD | 名称 | 对源码的影响 |
|---|---|---|
0x1012 |
COB-ID time stamp object | bit31 使能 consumer;bit30 使能 producer;bits0..10 是 CAN-ID |
0x1013 |
High resolution time stamp | 高分辨率时间戳对象;当前上传的 CO_TIME.* 源码没有处理它 |
在 CANopenNode 的 CO_TIME.h 中,TIME 的 producer/consumer 角色完全由 0x1012 解析得到:
1 | bit31 = 1 -> isConsumer = true |
4. 源码阅读地图
4.1 四个文件各看什么
| 文件 | 先看位置 | 作用 |
|---|---|---|
CO_SYNC.h |
CO_SYNC_t、CO_SYNC_status_t、CO_SYNCsend()、CO_SYNC_process() 声明 |
理解 SYNC 对象状态、返回值和发送行为 |
CO_SYNC.c |
CO_SYNC_receive()、OD_write_1005()、OD_write_1019()、CO_SYNC_init()、CO_SYNC_process() |
理解接收中断、OD 动态写、初始化、周期处理 |
CO_TIME.h |
CO_TIME_t、CO_TIME_set()、CO_TIME_process() 声明 |
理解 TIME 对象字段、设置当前时间和 producer 周期 |
CO_TIME.c |
CO_TIME_receive()、OD_write_1012()、CO_TIME_init()、CO_TIME_process() |
理解接收、OD 动态写、初始化、时间推进和发送 |
4.2 两个模块的共同模式
1 | flowchart TB |
这也是 CANopenNode 很常见的设计方式:中断回调只做轻量接收和置标志,实际协议状态推进放到周期性 process() 里。
5. SYNC 源码运行流程
5.1 初始化:CO_SYNC_init()
CO_SYNC_init() 做的事情可以按对象字典展开:
1 | flowchart TB |
关键点:
0x1005决定 SYNC CAN-ID,并在启用 producer 时决定isProducer。0x1006对 producer 是必要参数;对纯 consumer,可用于超时监控。0x1007是可选参数;为空或为0时不启用同步窗口。0x1019是可选参数;源码会把非法值做约束:1修正为2,大于240修正为240。- RX buffer 注册的回调是
CO_SYNC_receive()。
5.2 接收中断:CO_SYNC_receive()
接收回调只做三类事情:
1 | flowchart TB |
这里最重要的变量是:
| 变量 | 含义 |
|---|---|
CANrxNew |
通知 CO_SYNC_process() 有新 SYNC 到达 |
CANrxToggle |
给同步 RPDO 双缓冲使用;每个 SYNC 边沿翻转 |
receiveError |
延迟到主循环里上报 EMCY,避免中断里做重处理 |
counter |
启用 1 字节 counter 时保存收到的 counter |
CANrxToggle 容易被忽略。源码注释说明,同步 RPDO 需要双接收缓冲;SYNC 边沿到来时切换 buffer,使“正在接收”和“正在处理”的 RPDO buffer 分离。
5.3 发送:CO_SYNCsend()
CO_SYNCsend() 是 CO_SYNC.h 中的 static inline 函数。它的行为很短:
1 | flowchart TB |
注意:即使 counterOverflowValue == 0,TX buffer 的 DLC 已在初始化时配置为 0,因此 data[0] 写入不会让帧变成 1 字节帧。帧长度由 CO_CANtxBufferInit() 的 DLC 参数决定。
5.4 周期处理:CO_SYNC_process()
CO_SYNC_process() 是 SYNC 的主状态推进函数:
1 | flowchart TB |
返回值只有三种:
| 返回值 | 含义 | 上层怎么用 |
|---|---|---|
CO_SYNC_NONE |
本轮没有 SYNC 事件 | 常规空转 |
CO_SYNC_RX_TX |
本轮收到或发送了 SYNC | 驱动同步 PDO / 应用同步动作 |
CO_SYNC_PASSED_WINDOW |
本轮刚越过同步窗口 | 可用于禁止窗口外同步 PDO 或触发检查 |
5.5 consumer 超时监控
当 0x1006 非零、当前节点不是 producer,并且已经收到过至少一次 SYNC 后,源码开始超时监控:
1 | periodTimeout = 1.5 × OD_1006_period |
收到新的 SYNC 后,如果之前处于超时错误状态,则调用 CO_errorReset() 清除错误。
5.6 0x1005 / 0x1019 动态写入
如果启用了 CO_CONFIG_FLAG_OD_DYNAMIC,写对象字典时会进入扩展写函数:
1 | flowchart TB |
这里的限制很实际:0x1019 会改变 SYNC 帧 DLC,源码要求在通信周期未启用时修改,避免运行中改变帧格式造成网络不一致。
6. TIME 源码运行流程
6.1 初始化:CO_TIME_init()
CO_TIME_init() 的主线比 SYNC 更短:
1 | flowchart TB |
关键点:
- TIME 的启停方向由
0x1012控制,不是由 Node-ID 控制。 - consumer 使能后才注册接收 buffer。
- producer 相关编译能力还受
CO_CONFIG_TIME_PRODUCER控制;编译时没打开 producer,运行时bit30也不能让它真正发送。 - TX buffer 的 DLC 固定是
CO_TIME_MSG_LENGTH = 6。
6.2 接收中断:CO_TIME_receive()
TIME 的接收回调只接受 6 字节帧:
1 | flowchart TB |
TIME 源码没有像 SYNC 那样记录接收长度错误。长度不等于 6 时直接忽略。
6.3 周期处理:CO_TIME_process()
CO_TIME_process() 有三段逻辑:
1 | flowchart TB |
6.4 接收到 TIME 后如何解析
源码解析逻辑:
1 | uint32_t ms_swapped = CO_getUint32(&TIME->timeStamp[0]); |
要点:
ms只取低 28 bit。days是 16 bit。- 收到 TIME 后清零
residual_us,避免前一次本地微秒余数污染对时结果。 - 返回值
true只表示本轮刚收到并处理了 TIME stamp,不表示 producer 已发送 TIME。
6.5 没有收到 TIME 时如何本地走时
如果本轮没有新 TIME,源码用 timeDifference_us 累加本地时间:
1 | uint32_t us = timeDifference_us + TIME->residual_us; |
当 ms 超过一天时:
1 | if (TIME->ms >= 24h_ms) { |
这说明 TIME consumer 并不是“只靠网络 TIME 才能走时”。收到 TIME 是校准点;两次校准之间,它仍靠本地周期时间差继续推进。
6.6 TIME producer 如何启动
TIME producer 需要两件事同时成立:
1 | 编译期:CO_CONFIG_TIME_PRODUCER 打开 |
CO_TIME_set() 做了三件事:
1 | TIME->residual_us = 0; |
因此,producer 至少要先调用一次 CO_TIME_set() 设置当前时间和发送周期。只配置 0x1012 bit30 不足以产生周期 TIME 帧。
7. TIME 与 SYNC 在主循环中的关系
在 CANopenNode 的常规处理模型中,SYNC 通常先被处理,再用它的结果驱动同步 PDO:
1 | flowchart TB |
SYNC 和 TIME 没有直接依赖关系:
| 问题 | 看哪个对象 |
|---|---|
| 周期 PDO 为什么没发? | 先看 SYNC、PDO 传输类型、NMT 状态 |
| 网络时间为什么没更新? | 先看 TIME、0x1012、DLC 是否为 6 |
| 同步窗口外 PDO 为什么被丢? | 看 0x1007、syncIsOutsideWindow、PDO 处理顺序 |
| 节点是否收到过 SYNC? | 看 CO_SYNC_process() 返回值和 timeoutError |
| TIME producer 为什么没发? | 看 CO_TIME_set() 是否设置了非零 producerInterval_ms |
8. 最小心智模型
1 | flowchart TB |
记住这三条就不容易读偏:
SYNC看0x1005/0x1006/0x1007/0x1019,核心是“周期边界 + 同步窗口 + counter”。TIME看0x1012,核心是“6 字节时间戳 + 本地走时 + 可选周期发送”。- CANopenNode 的
receive()不是完整协议处理,它只是中断短路径;真正的状态变化主要在process()。
参考资料
- CiA 301 V4.2.0(中文注释版),用户上传 PDF:第 7.2.5、7.2.6、7.5.2.5、7.5.2.6、7.5.2.7、7.5.2.15、7.5.2.22 节。
- CAN in Automation: Special function protocols, https://www.can-cia.org/can-knowledge/special-function-protocols
- CAN in Automation: SYNC protocol, https://www.can-cia.org/can-knowledge/sync-protocol
- CANopenNode Doxygen: SYNC, https://canopennode.github.io/CANopenNode/group__CO__SYNC.html
- CANopenNode Doxygen: TIME, https://canopennode.github.io/CANopenNode/group__CO__TIME.html
- CANopenNode GitHub repository, https://github.com/CANopenNode/CANopenNode
- 上传源码:
CO_SYNC.h、CO_SYNC.c、CO_TIME.h、CO_TIME.c















