Lely CANopen:CSDO/SSDO 客户端服务器 SDO 状态机、分段与块传输机制
@[toc]
一句话结论
Lely 将 SDO 实现拆成客户端和服务器两个事件驱动状态机:
co_csdo_t主动发起远程上传或下载,负责构造请求帧、等待响应、维护超时、toggle、块序号、CRC、进度与完成回调;co_ssdo_t被动接收客户端请求,负责解析命令、定位对象字典、调用co_sub_dn_ind()/co_sub_up_ind(),并生成成功响应或 SDO abort;- 两者都依赖
can_net_t的 receiver、timer 和 send callback,本身不创建线程,也不直接操作 SocketCAN; - 快速传输只有初始化帧,分段传输每帧最多携带 7 字节并交替 toggle bit,块传输以最多 127 个 segment 组成一个 block,通过
ackseq、blksize和可选 CRC 实现 Go-Back-N 式恢复; - CSDO 的完成回调在状态机回到空闲态后触发,因此回调中可以提交下一次请求;SSDO 没有服务级完成回调,其应用语义集中在对象字典 indication;
- 动态修改
0x1200..0x12FF会中止正在进行的传输并重新注册 CAN receiver,参数更新不是透明操作。
最重要的运行模型是:
1 | application request or incoming CAN frame |
1. 四个核心文件的职责边界
1.1 include/lely/co/csdo.h
该头文件公开 Client-SDO API:
1 | local object dictionary request helpers |
它是主站侧或其他 SDO 客户端主动访问远端对象字典的底层 C 接口。
1.2 src/co/csdo.c
该文件实现:
1 | CSDO service object |
1.3 include/lely/co/ssdo.h
该头文件公开 Server-SDO 的生命周期、参数查询和 timeout 配置接口。与 CSDO 相比,它没有“主动上传/下载”API,因为 SSDO 的动作由收到的客户端请求触发。
1.4 src/co/ssdo.c
该文件实现:
1 | SSDO service object |
1.5 与 sdo.c 的分层关系
1 | flowchart TD |
csdo.c/ssdo.c 知道协议命令字、toggle、timeout、block sequence 和 CRC;sdo.c 只负责请求片段、数据聚合和类型序列化。
2. CSDO 与 SSDO 的方向不要混淆
CANopen 使用“上传/下载”描述相对于 Server-SDO 对象字典的数据方向:
| 操作 | 数据方向 | CSDO 行为 | SSDO 行为 |
|---|---|---|---|
| SDO download | Client → Server | 发送本地数据 | 写远端对象字典 |
| SDO upload | Server → Client | 接收远端数据 | 读取本地对象字典 |
因此:
1 | co_csdo_dn_req() |
CSDO 是 Client-SDO,SSDO 是 Server-SDO。官方网页的个别文字可能把 Server-SDO 后的缩写误写为 CSDO,但源码类型名和头文件边界是明确的:co_csdo_t 对应客户端,co_ssdo_t 对应服务器。
3. SDO 连接参数与对象字典
3.1 参数对象范围
| 角色 | 对象范围 | 典型对象 |
|---|---|---|
| Server-SDO 参数 | 0x1200..0x127F |
0x1200 为默认 SSDO |
| Client-SDO 参数 | 0x1280..0x12FF |
0x1280 为第一个 CSDO |
参数记录由 struct co_sdo_par 表示:
1 | struct co_sdo_par { |
字段含义:
| 字段 | 含义 |
|---|---|
n |
最高支持子索引 |
cobid_req |
Client → Server 请求 COB-ID |
cobid_res |
Server → Client 响应 COB-ID |
id |
对端 Node-ID |
3.2 默认连接集
当 Node-ID 为 N 时,默认 SDO 使用:
1 | request COB-ID = 0x600 + N |
例如 Node-ID 1:
1 | Client → Server 0x601 |
3.3 位 31 的语义
CO_SDO_COBID_VALID 的名称容易误导。该位被置位时表示对应 COB-ID 无效或服务不存在;请求与响应 COB-ID 的 invalid bit 都清零,服务才可用。
4. 两个服务对象内部保存了什么
4.1 struct __co_csdo
CSDO 的主要字段可分为六组:
| 组别 | 字段 | 作用 |
|---|---|---|
| 连接 | net、dev、num、par |
CAN 网络、对象字典和 SDO 参数 |
| 事件源 | recv、timer、timeout |
响应接收和协议超时 |
| 状态机 | state、ac、idx、subidx |
当前状态、abort code 和对象地址 |
| 分段 | size、toggle |
总长度与 toggle bit |
| 块传输 | blksize、pst、ackseq、crc |
block 协商、恢复和 CRC |
| 缓冲/回调 | dn_buf、up_buf、buf、各类 callback |
下载源、上传目标、序列化和通知 |
关键区别:
dn_buf通常直接引用用户传给co_csdo_dn_req()的缓冲区;up_buf指向上传结果缓冲区,默认使用 CSDO 自己的buf;buf也用于co_csdo_dn_val_req()把类型值序列化为字节流;- 一个 CSDO 同时只允许一个进行中的请求。
4.2 struct __co_ssdo
SSDO 保存:
| 组别 | 字段 | 作用 |
|---|---|---|
| 连接 | net、dev、num、par |
本地服务器和 SDO 参数 |
| 事件源 | recv、timer、timeout |
请求接收和协议超时 |
| 状态机 | state、idx、subidx |
当前协议状态和访问地址 |
| 分段 | toggle、req |
toggle 和对象访问请求 |
| 块传输 | blksize、ackseq、gencrc、crc |
block 接收/发送状态 |
| 缓冲 | buf、nbyte |
segment/block 临时数据与当前 request 消费位置 |
SSDO 不保存完成回调。完成或失败最终表现为:
1 | CAN response or abort frame |
5. Lely 的状态机不是 switch(enum state)
官方 FSM 文档说明,Lely 服务对象使用函数指针驱动的事件状态机。CSDO 和 SSDO 都为每个状态定义一组处理函数:
1 | on_enter |
CSDO 的状态结构包含完整的 enter/leave 钩子;SSDO 状态结构只包含 abort、time 和 recv 转移。
5.1 CSDO 状态进入逻辑
co_csdo_enter() 支持瞬态状态:
1 | set next state |
abort_state 就是典型瞬态状态:
- 进入时停止 timer;
- 立即返回
wait_state; - 离开
abort_state时调用完成 callback。
这意味着完成 callback 被调用时,CSDO 已经回到 idle 状态。应用可以在 callback 内提交下一次 SDO 请求,而不会因“上一请求仍忙”被拒绝。
5.2 SSDO 状态进入逻辑
co_ssdo_enter() 只替换 state 指针。初始化请求不是长期等待状态:SSDO 在 wait_state 收到 initiate frame 后直接调用对应处理函数,再进入 segment 或 block 状态。
这是客户端和服务器状态结构不完全对称的原因:
- Client 发起请求后必须等待远端 initiate response;
- Server 收到 initiate request 时可以同步完成解析、对象访问和首个响应。
6. CSDO 状态集合
CSDO 定义的主要状态:
1 | stopped |
总览:
1 | flowchart TD |
快速传输不单独定义状态,它在 initiate 状态收到响应后直接完成。
7. SSDO 状态集合
SSDO 的长期状态更少:
1 | stopped |
download initiate、upload initiate、block download initiate 和 block upload initiate 只是 wait_state 内部直接调用的处理函数,并未实例化为独立长期状态。
1 | flowchart TD |
8. 创建、启动、停止和销毁
8.1 CSDO 创建
co_csdo_create(net, dev, num) 最终执行:
1 | validate num |
当 dev == NULL 时,num 不再表示 SDO 编号,而是 Node-ID,范围为 1..127。CSDO 直接使用默认请求/响应 COB-ID,因此主站应用无需先构造 0x1280 参数对象。
当 dev != NULL 时,num 表示 1..128 的 Client-SDO 编号,对应参数对象必须存在。
8.2 SSDO 创建
co_ssdo_create(net, dev, num) 要求本地 co_dev_t 必须存在。对于第一个 SSDO:
0x1200参数对象可以不存在;- 服务使用本地 Node-ID 推导默认 COB-ID。
对于第 2 到第 128 个 SSDO,对应 0x1200 + num - 1 参数对象必须存在。
8.3 start() 做了什么
CSDO:
1 | copy 0x1280..0x12FF parameters |
SSDO:
1 | enter waiting state |
8.4 stop() 做了什么
两个服务都会:
- 用
CO_SDO_AC_NO_SDO中止当前传输; - 停止 timer;
- 注销 CAN receiver;
- 移除参数对象的 download indication;
- 进入 stopped 状态。
CSDO 停止时,进行中请求的 confirmation callback 会收到 abort code;SSDO 停止时会清理协议状态,但没有服务级用户 completion callback。
9. can_net_t 是协议栈与平台之间的边界
CSDO/SSDO 都只依赖三个 can_net_t 能力:
1 | can_recv_start / can_recv_stop |
完整运行链路是:
1 | flowchart LR |
因此创建 CSDO/SSDO 并不代表总线已经工作。应用必须:
- 把收到的 CAN 帧喂给
can_net_recv(); - 把协议栈产生的发送帧写到实际 CAN 驱动;
- 持续调用
can_net_set_time()推进 timer。
如果只接入 CAN 收发但不推进时间,SDO timeout 永远不会触发。
10. 本地对象访问 API 与远程 CSDO 不同
csdo.h 同时提供:
1 | co_dev_dn_req / co_dev_dn_val_req / co_dev_dn_dcf_req |
这些函数名称位于 CSDO 头文件中,但它们不会发送 CAN 帧,也不会进入 CSDO 状态机。
本地下载流程:
1 | find object and sub-object |
本地上传流程:
1 | find object and sub-object |
重要区别:
| 维度 | co_dev_*_req() |
co_csdo_*_req() |
|---|---|---|
| 目标 | 本地对象字典 | 远端对象字典 |
| CAN 报文 | 无 | 有 |
| 完成方式 | 当前调用内同步完成 | 由后续 CAN/time 事件异步完成 |
callback 中 sdo |
NULL |
当前 CSDO 指针 |
| timeout | 无 | 可配置 |
应用不能根据函数名中的 req 就假定它一定异步。
11. CSDO 提交请求前的统一检查
正常和块传输都先通过内部请求初始化函数:
1 | service must be valid |
若连接无效或已有请求正在进行,函数返回 -1 并设置 ERRNUM_INVAL,不会排队等待。
因此一个 co_csdo_t 同时只能处理一个请求。并行访问多个节点或多条 SDO 通道时,应创建多个 CSDO service,或在上层实现请求队列。
12. 快速下载:Client 写入 1 到 4 字节
co_csdo_dn_req() 根据长度选择协议:
1 | 1..4 bytes expedited download |
快速下载流程:
1 | flowchart LR |
典型 4 字节下载命令字为 0x23:
1 | byte 0 command specifier |
SSDO 收到快速下载后:
- 解析 index/sub-index 和数据长度;
- 将
req.buf直接指向当前 CAN frame 的数据区; - 调用
co_sub_dn_ind(); - 成功则发送 download initiate response;
- 失败则发送 SDO abort。
req.buf 只在同步 indication 调用期间有效,应用 callback 不能保存该指针供以后使用。
13. 0 字节值为什么走分段下载
CSDO 的快速路径条件是:
1 | size != 0 and size <= 4 |
因此 0 字节对象不会使用 expedited transfer。分段状态会专门发送一个空的最后 segment,并借助 toggle 状态确认它确实已经发送。
这不是普通应用的常见对象类型,但对 DOMAIN、空文件或自定义对象测试很有价值。
14. 普通分段下载状态机
14.1 CSDO 侧
1 | send download initiate request with total size |
每次进入 download segment 状态,CSDO 从 dn_buf 计算剩余字节数,最多发送 7 字节,并在等待响应前重新启动 timeout。
14.2 toggle 检查
发送 segment 时:
1 | command contains current toggle |
收到 server response 时,源码检查响应 toggle 是否已经变化。若不符合预期,发送 CO_SDO_AC_TOGGLE abort。
14.3 SSDO 侧
SSDO 在 initiate 阶段先调用一次 download indication,使对象回调有机会仅根据:
1 | req.size |
预先拒绝非法长度或当前状态下不允许的写入。
随后每收到一个 segment:
1 | verify command |
这说明 custom download indication 必须支持“首次只通知总长度,后续再逐段提交数据”的调用模式。
15. 快速上传:Server 返回 1 到 4 字节
CSDO 发送 upload initiate request,SSDO 定位对象并调用 co_sub_up_ind()。
若 req.size 为 1..4:
1 | SSDO gathers the complete value |
CSDO 的 upload confirmation 获得:
1 | const void *ptr; |
该指针通常指向 CSDO 内部 membuf,其内容可能在下一次上传或对象销毁时失效。需要长期保存时,callback 内必须复制。
16. 普通分段上传状态机
16.1 SSDO 侧数据生产
SSDO 的 co_ssdo_up_buf() 不要求对象 indication 一次返回完整值。它可以反复:
1 | consume current req.buf |
因此应用可通过多次 upload indication 流式提供较大 DOMAIN 数据。
16.2 CSDO 侧数据聚合
CSDO 在 initiate response 中读取声明的总长度,预留上传缓冲区,然后逐段请求:
1 | send upload segment request |
源码对收到的数据执行上限和最终长度检查:
- 超过声明长度:
CO_SDO_AC_TYPE_LEN_HI; - last segment 到达但总长度不足:
CO_SDO_AC_TYPE_LEN_LO。
工程上应保证 Server-SDO 在普通分段上传中提供准确 size indication。当前 CSDO 实现使用该长度完成缓冲预留和边界判断。
17. Client-SDO block download
块下载用于 Client 向 Server 发送大数据。
17.1 初始化
CSDO 发送:
1 | block download initiate |
SSDO:
- 解析对象地址和总长度;
- 选择 block size;
- 让 download indication 预检大小;
- 返回 block size 和 CRC 支持情况。
17.2 子块发送
CSDO 在一个 block 内连续发送多个 segment,不等待每帧响应:
1 | seqno 1 |
每个 segment 最多 7 字节,第 7 位标记最后 segment,低 7 位为 sequence number。
17.3 Go-Back-N 式恢复
SSDO 只接收连续 sequence:
1 | seqno == ackseq + 1 |
乱序或丢失后的 segment 不会写入对象数据。block 结束时,SSDO 返回最后连续接收成功的 ackseq。
若:
1 | ackseq < blksize |
CSDO 将 dn_buf.cur 回退到缺失位置,并从下一轮 block 重发未确认部分。
17.4 结束与 CRC
最后一个 block 完成后:
- Client 发送 end request,携带末段有效字节数和可选 CRC;
- Server 检查总长度;
- Server 检查最后 segment 有效字节数;
- Server 校验 CRC;
- Server 将最后数据提交 indication;
- Server 返回 end response。
1 | flowchart TD |
18. Client-SDO block upload
块上传用于 Client 从 Server 读取大数据。
18.1 CSDO 初始参数
co_csdo_blk_up_req() 初始化:
1 | pst protocol switch threshold |
Client 把最大可接收 block size 发给 Server。
18.2 block size 兼容退让
若 Server 用 CO_SDO_AC_BLK_SIZE 拒绝 block size,CSDO 不立即失败,而是把大小减半后重试:
1 | 127 → 64 → 32 → 16 → ... → 1 |
源码特别保证第一次从 127 变为 64。
18.3 Server-induced protocol switch
若 CSDO 请求 block upload,但收到普通 upload initiate response,它会直接转入普通上传处理。这是 Server 主动降级到 expedited/segmented upload 的协议切换。
18.4 子块接收和确认
CSDO 只复制连续 sequence segment:
1 | seqno == ackseq + 1 |
达到 block size 或遇到 last segment 时,Client 返回 ackseq。Server 根据确认:
- flush 已确认的 segment;
- 保留未确认数据;
- 使用新的
blksize生成下一 block。
18.5 结束校验
CSDO 在 block upload end response 中检查:
1 | total length |
成功后发送 end confirmation,并触发 upload completion callback。
19. Protocol Switch Threshold 的实际作用
PST 只用于 block upload。
Client 提供非零 pst 后,如果 Server 发现对象长度:
1 | req.size <= pst |
SSDO 可以切换为普通 SDO upload:
size <= 4:expedited upload;size > 4:segmented upload。
PST 的目的不是改变对象数据,而是避免小对象承担 block 协议额外开销。
20. timeout 的真实计时边界
CSDO/SSDO 的 timeout 都不是后台线程定时器。它依赖 can_net_t 时间推进。
20.1 CSDO
CSDO 在发送需要响应的请求后启动或重启 timer:
1 | initiate request |
超时后进入当前状态的 on_time(),通常:
1 | send abort with CO_SDO_AC_TIMEOUT |
20.2 SSDO
SSDO 在已经建立传输、等待 Client 下一步请求时启动 timer。空闲 waiting 状态不使用传输 timeout。
例如:
- download initiate response 后等待第一个 segment;
- upload segment response 后等待下一个 request;
- block response 后等待下一 block 或 end request。
20.3 每个 CSDO/SSDO 一个 timer
单个服务对象只有一个协议 timer,与“一个服务对象同时只允许一个传输”的设计一致。
21. abort 是协议错误,也是统一完成路径
SDO abort frame 的布局为:
1 | byte 0 0x80 |
21.1 CSDO 的两类 abort
1 | abort indication |
co_csdo_abort_ind() 保存最终 ac 并进入瞬态 abort state。成功完成也使用 ac == 0 经过同一完成路径。
21.2 SSDO 的两类 abort
1 | co_ssdo_abort_res |
成功完成同样通过 co_ssdo_abort_ind() 清理 index、toggle、block、CRC、request 和 buffer,然后回到 waiting。
21.3 常见 abort 来源
| Abort code | 典型触发点 |
|---|---|
CO_SDO_AC_NO_CS |
command specifier、帧长度或 subcommand 非法 |
CO_SDO_AC_TOGGLE |
分段 toggle 不符合预期 |
CO_SDO_AC_TIMEOUT |
等待下一请求或响应超时 |
CO_SDO_AC_TYPE_LEN_HI |
收到数据超过声明长度 |
CO_SDO_AC_TYPE_LEN_LO |
last 到达但数据不足 |
CO_SDO_AC_BLK_SIZE |
block size 非法或不支持 |
CO_SDO_AC_BLK_SEQ |
block sequence number 非法 |
CO_SDO_AC_BLK_CRC |
block CRC 不匹配 |
CO_SDO_AC_NO_OBJ |
index 不存在 |
CO_SDO_AC_NO_SUB |
sub-index 不存在 |
22. 完成 callback 与进度 indication
22.1 下载确认
1 | co_csdo_dn_con_t( |
ac == 0 表示成功。远程请求时 sdo 为当前服务对象,本地 co_dev_dn_req() 时为 NULL。
22.2 上传确认
上传 callback 额外获得结果缓冲区:
1 | const void *ptr; |
失败时源码传递:
1 | ptr = NULL |
22.3 进度 indication
co_csdo_ind_t 提供:
1 | total size |
其触发规则:
- expedited transfer 不产生进度通知;
- 确定总长度后通知一次;
- 之后按 block 边界或最多 127 个 segment 的区间通知;
- 最后一次 progress 在 completion callback 前发生。
进度 callback 应保持轻量,不应阻塞 CAN event loop。
23. completion callback 的重入边界
CSDO 的 callback 在 abort transient state 离开时执行,此时状态已经切换到 waiting。因此下面的模式是可行的:
1 | static void on_upload(...) |
但需要避免:
- 在 callback 内销毁仍被外层调用栈使用的
can_net_t; - 多线程同时对同一 CSDO 调用 request/stop/destroy;
- 未复制上传数据就提交下一次可能复用内部
membuf的请求。
24. 参数对象动态更新
CSDO 给 0x1280..0x12FF 安装 co_1280_dn_ind();SSDO 给 0x1200..0x127F 安装 co_1200_dn_ind()。
更新流程:
1 | SDO or local write to parameter object |
24.1 COB-ID 修改限制
源码拒绝:
1 | old COB-ID valid |
正确顺序是:
1 | set invalid bit |
24.2 扩展帧检查
若 COB-ID 中出现 11-bit 之外的 CAN-ID 位,却没有设置 frame bit,参数写入返回 CO_SDO_AC_PARAM_VAL。
24.3 更新会打断传输
co_csdo_update() 和 co_ssdo_update() 都先中止进行中的传输。应用不应在参数写成功后假定旧请求还能继续。
25. CSDO 下载缓冲区所有权
co_csdo_dn_req() 不复制用户提供的原始下载数据。内部 dn_buf 直接引用:
1 | const void *ptr; |
因此直到 confirmation callback 发生前,应用必须保证:
- 缓冲区仍存在;
- 内容不被修改;
- DMA、其他线程或栈退出不会使地址失效。
错误示例:
1 | void send_config(co_csdo_t *sdo) |
安全做法是使用静态、堆、对象成员或请求上下文持有的缓冲区,并在 callback 后释放。
co_csdo_dn_val_req() 会先把类型值序列化到 CSDO 内部 buf,因此不直接持有调用者标量地址;但同一 CSDO 仍不能并行提交其他请求。
26. SSDO request buffer 生命周期
SSDO 的 req.buf 可能指向:
1 | incoming CAN frame data |
custom indication 只能在当前同步调用内读取该指针。若要交给工作线程处理,必须先复制。
对于异步硬件写入,推荐:
1 | indication validates and copies bytes |
但这意味着 SDO 成功响应只表示“数据已被应用接收”,不一定表示外设操作已经完成。若业务要求远程确认硬件结果,应在 indication 内完成必要操作或设计额外状态对象。
27. 无动态内存配置
27.1 CSDO
当 LELY_NO_MALLOC 启用时,默认:
1 | CO_CSDO_MEMBUF_SIZE = 8 bytes |
足以序列化基本数据类型,但不足以自动保存较大的 typed value 或 upload result。应用需要评估并调整宏或避免大对象。
27.2 SSDO
当禁用动态内存时:
1 | block disabled default 7 bytes |
最大 sequence number 为 127 时,默认 block buffer 为:
1 | 127 * 7 = 889 bytes |
这对小型 MCU 已经不是可忽略的内存。若减小 CO_SSDO_MEMBUF_SIZE,CO_SSDO_MAX_SEQNO 也会相应受限,客户端必须接受更小 block size。
28. 条件编译边界
| 宏或配置项 | 影响 |
|---|---|
LELY_NO_CO_CSDO |
移除 Client-SDO 支持,同时影响依赖 CSDO 的 master 功能 |
LELY_NO_CO_SSDO_BLK |
移除 Server-SDO block transfer |
LELY_NO_MALLOC |
使用固定 membuf,限制可处理对象和 block 大小 |
LELY_NO_CANFD |
决定是否编译 CAN FD 帧过滤分支 |
LELY_NO_STDIO |
移除部分 trace 字符串格式化 |
LELY_NO_ERRNO |
降低系统错误到 errno 的细分能力 |
CSDO/SSDO 均忽略 RTR 帧和 CAN FD format 帧,本文分析的是 CiA 301 经典 CAN SDO。
29. 线程安全和串行化要求
源码未在 CSDO/SSDO 内部提供 mutex。推荐所有下列操作由同一 CAN event loop 线程执行:
1 | can_net_recv |
如果业务线程需要发起 SDO,应向 event loop 投递命令,而不是直接并发调用同一 service。
主要竞态风险:
- request 提交与 response callback 同时修改 state;
- parameter update 中止传输时,另一线程仍使用 buffer;
- stop/destroy 与 receiver/timer callback 并发;
- completion callback 释放上层上下文,而其他线程仍访问。
30. CSDO 完整下载流程
1 | flowchart TD |
31. SSDO 完整请求处理流程
1 | flowchart TD |
38. 完整机制总图
1 | flowchart TD |
39. 最终心智模型
可以将 CSDO/SSDO 压缩为五层:
1 | 第 1 层:平台适配 |
最简洁的结论是:
co_csdo_t是“主动请求、等待响应和通知应用”的客户端状态机;co_ssdo_t是“接收请求、访问本地对象字典和生成响应”的服务器状态机。两者通过can_net_t获得 CAN 与时间事件,通过co_sdo_req交换对象值,通过统一 abort code 表达所有协议、长度、权限和应用错误。理解 receiver、timer、state、buffer 和 indication 五个边界后,快速、分段和块 SDO 都只是同一事件状态机上的不同传输路径。
参考源码与资料
核心源码
关联源码
Lely 官方网页
协议参考
- CiA 301 V4.2.0:7.2.4 SDO 服务与协议;7.5.2.33 Server-SDO 参数;7.5.2.34 Client-SDO 参数。
- CiA 302-3 V4.1.0:对象
0x1F22concise DCF。
前置学习笔记
Lely_CANopen_co_obj_co_sub_对象字典节点_机制原理与运行流程.mdLely_CANopen_co_dev_DCF_EDS解析_对象字典设备描述_机制原理与运行流程.mdLely_CANopen_co_sdo_req_PDO映射编解码_对象字典访问桥接机制.mdLely_CANopen_RPDO_TPDO_运行期收发_SYNC_事件与定时器_机制原理与运行流程.md









