Lely CANopen NMT Boot Slave:从 `nmt_boot.c` 看主站如何识别、配置并启动从站
Lely CANopen NMT Boot Slave:从 nmt_boot.c 看主站如何识别、配置并启动从站 @[toc] 1. 先给出核心判断nmt_boot.c 实现的不是普通从站侧 NMT 状态机,也不是简单地发送一条 Start Remote Node 命令。它是 NMT manager 针对一个远端从站执行的启动编排状态机。 对每个被管理的从站,它把多种已有服务串成一条完整链路: 1234567891011121314151617主站 DCF 中的从站声明 ↓确认该节点是否允许/需要 boot ↓通过 Client-SDO 校验设备类型与 Identity ↓通过 Heartbeat 或 Node Guarding 判断节点状态 ↓必要时发送 Reset Communication 并等待 boot-up ↓按配置决定是否检查、停止、清除和下载程序 ↓比较配置日期/时间,必要时调用 configuration request ↓启动 Heartbeat/Node Guarding 错误控制 ↓向 co_nmt_t...
Lely CANopen:CSDO&SSDO 客户端服务器 SDO 状态机、分段与块传输机制
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 没有服务...
Lely CANopen:RPDO&TPDO 运行期收发、SYNC、事件与定时器机制原理和运行流程
Lely CANopen:RPDO/TPDO 运行期收发、SYNC、事件与定时器机制原理和运行流程 @[toc] 源码基线:lely-industries/lely-core,提交 620d1858eb8520dbc3dc5e1a7314565becd54199,查阅时间为 2026-07-30。 本文核心分析以下文件: 123456include/lely/co/rpdo.hinclude/lely/co/rpdo.hppsrc/co/rpdo.cinclude/lely/co/tpdo.hinclude/lely/co/tpdo.hppsrc/co/tpdo.c 前置文章已经说明 co_pdo_dn()、co_pdo_up()、PDO 映射校验、位级编解码以及对象字典 indication 桥接。本文不重复展开这些基础算法,而是集中分析 RPDO/TPDO 服务对象如何注册 CAN 接收器、维护运行期参数、处理 SYNC、事件定时器、接收超时、同步窗口、抑制时间、RTR 和用户回调。 事实边界:函数、字段、条件分支和调用链来自上述源码;并发风险、生...
Lely CANopen `coapp Device` 本地 SDO PDO 读写、远程对象映射与运行机制
Lely CANopen coapp::Device:本地 SDO/PDO 读写、远程对象映射与运行机制 @[toc] 1. 先给出最关键的结论理解 Device 时,必须先把“本地读写”“远程读写”“SDO”“PDO”分开: Device 本质上是 co_dev_t 的 C++ 所有权与访问封装。 Device::Read()、Device::Write() 是对本地对象字典执行一次“本地 SDO 请求”,不会发送 CAN 帧。 Device::Get()、Device::Set() 是直接访问本地对象字典值,绕过 SDO 访问权限、范围检查和下载/上传回调。 Device::RpdoRead() 并不会通过 CAN 去读取远程节点,而是把“远程 TPDO 对象地址”转换为本地 RPDO 映射对象,再读取本地缓存值。 Device::TpdoWrite() 也不会执行远程 SDO 下载,而是把“远程 RPDO 对象地址”转换为本地 TPDO 映射对象,再写入本地待发送值。 真正通过 CAN 总线访问远程对象字典的是 co_csdo_t / l...
Lely CANopen:co_dev 设备对象字典与 DCF 和 PDO 事件机制
Lely CANopen:co_dev 设备对象字典与 EDS/DCF 解析、concise DCF 和 PDO 事件机制 @[toc] 一句话结论Lely 的 co_dev_t 不是协议状态机,也不是简单的 EDS 文件句柄,而是 CANopen 设备描述在运行期的根对象: 它保存 Node-ID、网络号、设备名称、身份信息、支持的位速率、LSS 和 dummy PDO 类型等设备级元数据; 它用一棵以 16 位索引为 key 的红黑树管理全部 co_obj_t,每个 co_obj_t 再管理自己的 co_sub_t; dcf.c 先把 EDS/DCF 的 INI 文本解析为通用配置树,再逐层创建 co_dev_t → co_obj_t → co_sub_t; $NODEID + n 不只是文本替换,解析器会记录相应 flag,co_dev_set_id() 在 Node-ID 改变时遍历整棵对象字典并按差值修正相关值; 文本 EDS/DCF 与 concise DCF 是两种完全不同的格式:前者描述设备结构和元数据,后者是用于参数配置的一组...
Lely CANopen:can_net、io_can_net设计原理
Lely CANopen:can_net、io_can_net 与 CAN channel 的分层关系,以及 TX/RX Ring 的设计原理@[toc] 一句话结论Lely 中容易混淆的几个对象,实际处于不同层次: can_net_t 是纯内存中的 CAN 逻辑调度核心,负责协议定时器、CAN-ID 接收分发和发送回调;它不知道 SocketCAN、文件描述符、poll、executor 或真实 CAN 控制器。 io_can_chan_t 是抽象 CAN 通道接口;src/io2/linux/can_chan.c 是它在 Linux SocketCAN 上的具体后端,负责文件描述符、收发操作、I/O 就绪事件和写确认。 io_can_net_t 是桥接层,把 can_net_t、io_timer_t、io_tqueue_t 和 io_can_chan_t 连接起来,使同步的协议核心能够运行在异步 I/O 框架上。 lely::io::CanNet 只是 io_can_net_t* 的 C++ RAII 包装,不是另一套独立实现;核心机制仍然...
Lely CANopen:io_tqueue 与 pheap 的实现原理、运行流程
Lely CANopen:io_tqueue 与 pheap 的实现原理、运行流程及其与 Timer、can_net 的关系 @[toc] 一句话结论io_tqueue 是一个“多路逻辑定时器复用器”: 上层可以同时提交很多个不同绝对截止时间的 io_tqueue_wait; 中间用 pheap 维护“当前最早到期”的等待; 下层始终只占用 一个 io_timer_t; 底层 Timer 到期时,再回到 io_tqueue 批量完成所有 value <= now 的等待。 它与 can_net 串起来之后,又形成“两级最早截止时间压缩”: 123456789很多 can_timer_t ↓can_net.timer_heap ↓ 选出协议层最早一个io_tqueue.queue ↓ 再与其他 I/O 等待一起排序一个 io_timer_t ↓Linux timerfd / poll / executor 1. 为什么需要 io_tqueue如果没有 io_tqueue,最直观的设计是: 1234每一个逻辑等待 → 一个 io_tim...
Lely CANopen `spscring` 单生产者单消费者环形队列完整机制学习笔记
Lely CANopen spscring 单生产者单消费者环形队列完整机制学习笔记 @[toc] 1. 先明确边界:spscring 不是数据缓冲区SPSC 是 Single-Producer Single-Consumer,即单生产者、单消费者。 Lely 的 spscring 与常见“结构体内部直接包含数组”的环形队列不同。它只管理: 哪些索引可以由生产者写入; 哪些索引可以由消费者读取; 生产者何时把写入结果发布给消费者; 消费者何时把已读槽位归还给生产者; 队列由空变为可读或由满变为可写时,是否需要触发回调。 实际数据存储由调用者单独提供: 12struct spscring ring;struct io_can_frame *rxbuf; 两者的关系是: 12345spscring= 索引所有权与可见性控制器rxbuf= 真正保存 CAN 帧的内存数组 因此,调用 spscring_p_alloc() 只会得到一个可写索引,不会返回数据指针,也不会复制任何数据。 1234567flowchart LR Prod[Producer] -->|p...
Lely CANopen CAN 完整机制学习笔记
Lely CANopen IO2 CAN 与 SocketCAN 完整机制学习笔记 @[toc] 1. CANopen、liblely-co、io2 与 SocketCAN 的边界Lely CANopen 协议栈本身是被动状态机。它不直接拥有 Linux socket,也不自行创建线程。Linux 上的实际 I/O 由 liblely-io2 提供,任务调度由 liblely-ev 提供。 12345678910flowchart TB APP[业务代码 / CANopen Master] --> CO[liblely-co / coapp] CO --> NET[CAN network / protocol state machine] NET --> IO2[liblely-io2 abstract CAN API] IO2 --> CHAN[Linux CanChannel] CHAN --> SOCK[SocketCAN raw socket] SOCK --> KERNEL[Lin...
Lely CANopen 事件调度机制详解:从 `ev_task`、Executor、`std_exec` 到 `ev_loop`、Future 与 Poll
Lely CANopen 事件调度机制详解:从 ev_task、Executor、std_exec 到 ev_loop、Future 与 Poll @[toc] 0. 阅读前先记住六个结论 ev_task 只是“可执行工作”的载体,不负责排队、线程切换或等待。 ev_exec_t 是抽象 Executor 接口,本质是 C 语言虚函数表。 ev_std_exec 不是另一个事件循环,而是一个适配层:只要求后端实现 post/abort/on_task_init/on_task_fini,它补齐 dispatch/defer/run。 ev_loop 才拥有真正的全局任务队列、等待线程、Poll 线程和 outstanding work 状态。 ev_exec_run() 的关键价值不是简单调用回调,而是建立“当前线程正在这个 Executor 内执行任务”的 TLS 上下文。 Future、I/O、定时器最终都不会直接“执行用户逻辑”;它们把 ev_task 投递给 Executor,由 ev_loop 取出并运行。 可以先把整个系统压缩成下面这条链: 1234...








