从源码看 CANopenNode LSS:搞懂主从机运行流程
@[toc]
先给结论
LSS(Layer Setting Services)不是用来传过程数据的,也不是普通 SDO 配置。它解决的是设备还没有有效 Node-ID,或者需要统一切换 CAN 位率时,主站如何在 CAN 总线上找到并配置从站的问题。
在 CANopenNode 的 305 目录中,LSS 的运行核心可以压缩成三句话:
- 主站只通过 0x7E5 发命令,从站只通过 0x7E4 回响应。 这是 LSS 固定使用的两条 CAN 帧通道。
- 从站通过 0x1018 身份对象形成 128 bit LSS Address。 主站可以已知地址精准选择,也可以对未配置节点做 Fastscan。
- Node-ID 和 bit rate 先进入 pending 值。 Node-ID 通常要经过保存和通信复位后才真正成为 CANopen 栈运行用的 ID;bit rate 的激活更危险,必须保证全网节点同步切换。
1. LSS 解决的工程问题
在普通 CANopen 网络中,很多通信对象都依赖 Node-ID。例如心跳常见为 0x700 + Node-ID,SDO 服务器默认接收/发送 COB-ID 也与 Node-ID 相关。问题是:
- 新设备刚出厂可能没有 Node-ID。
- 多个同型号设备可能不能靠 Node-ID 区分。
- 有时需要在现场统一修改 bit rate。
- 如果 Node-ID 无效,就不能指望先用 SDO 写对象字典,因为 SDO 自身也需要 Node-ID。
所以 LSS 被设计成一个低层配置入口:即使设备还不能作为完整 CANopen 节点运行,仍能通过 LSS 从站逻辑接收主站配置。
CiA 的 LSS 说明中明确区分 LSS manager 和 LSS server,并说明 LSS manager 可修改 server 的 CANopen Node-ID、切换整个网络的数据率;其固定使用 0x7E5 作为 manager 到 server 的命令帧、0x7E4 作为 server 到 manager 的响应帧。CANopenNode 官方 305 组文档也把 LSS 描述为用于查询或修改 Node-ID、物理层 bit timing,以及与 0x1018 身份对象一致的 LSS address。
2. CANopen 设备模型中,LSS 站在哪里
理解 LSS 之前,要先分清 CANopen 的三层对象关系:
1 | CANopen 设备 |
《CiA301 V4.2.0》把 CANopen 设备描述为由通信、对象字典和应用程序组成:通信单元提供通信对象和底层网络传输能力;对象字典集合影响应用程序对象、通信对象和状态机行为的数据项;对象字典是通信对象和应用程序对象之间的接口。
LSS 的特殊点是:它与普通对象字典访问不同。普通 SDO 读写对象字典依赖 Node-ID,而 LSS 要处理的正是“Node-ID 不存在或需要改变”的阶段。因此,CANopenNode 让 LSS slave 可以在完整 CANopen 初始化之前先工作。
3. 305 目录源码分工
CANopenNode GitHub 仓库 305/ 目录中,LSS 相关核心文件主要有 5 个:
| 文件 | 角色 | 阅读重点 |
|---|---|---|
305/CO_LSS.h |
公共协议定义 | 命令码、错误码、LSS address、LSS 状态、bit timing 表、Node-ID 合法性宏 |
305/CO_LSSmaster.h |
主站对象和 API 声明 | 返回值、非阻塞调用模型、Fastscan 参数结构 |
305/CO_LSSmaster.c |
主站状态机实现 | 发送 0x7E5、等待 0x7E4、超时、选择节点、配置 Node-ID、配置 bit rate、Fastscan |
305/CO_LSSslave.h |
从站对象和 API 声明 | pendingBitRate、pendingNodeID、回调接口、未配置节点处理 |
305/CO_LSSslave.c |
从站接收和响应实现 | CAN RX 回调预处理、主循环处理、配置响应、保存回调、触发通信复位 |
源码阅读建议先看 CO_LSS.h,再看 CO_LSSslave.c,最后看 CO_LSSmaster.c。原因是从站更接近协议响应本质,主站源码里有更多异步状态机和 Fastscan 细节。
4. CO_LSS.h:公共协议层是整套流程的字典
CO_LSS.h 里最重要的是五类定义。
4.1 命令码决定 data[0] 的含义
LSS 所有请求/响应都是 8 字节 CAN 数据帧,data[0] 是 command specifier。源码中常见命令如下:
| 命令 | 值 | 方向 | 用途 |
|---|---|---|---|
CO_LSS_SWITCH_STATE_GLOBAL |
0x04 |
Master -> Slave(s) | 全局切换 waiting/configuration |
CO_LSS_SWITCH_STATE_SEL_VENDOR |
0x40 |
Master -> Slave(s) | 选择指定 LSS 地址:Vendor-ID |
CO_LSS_SWITCH_STATE_SEL_PRODUCT |
0x41 |
Master -> Slave(s) | 选择指定 LSS 地址:Product Code |
CO_LSS_SWITCH_STATE_SEL_REV |
0x42 |
Master -> Slave(s) | 选择指定 LSS 地址:Revision Number |
CO_LSS_SWITCH_STATE_SEL_SERIAL |
0x43 |
Master -> Slave(s) | 选择指定 LSS 地址:Serial Number |
CO_LSS_SWITCH_STATE_SEL |
0x44 |
Slave -> Master | 指定选择成功响应 |
CO_LSS_CFG_NODE_ID |
0x11 |
双向 | 配置 Node-ID,请求和响应复用同一命令码 |
CO_LSS_CFG_BIT_TIMING |
0x13 |
双向 | 配置 pending bit rate |
CO_LSS_CFG_ACTIVATE_BIT_TIMING |
0x15 |
Master -> Slave(s) | 激活 pending bit rate,无确认 |
CO_LSS_CFG_STORE |
0x17 |
双向 | 保存 pending Node-ID/bit rate |
CO_LSS_IDENT_FASTSCAN |
0x51 |
Master -> Slave(s) | Fastscan 请求 |
CO_LSS_IDENT_SLAVE |
0x4F |
Slave -> Master | Fastscan 应答 |
CO_LSS_INQUIRE_* |
0x5A..0x5E |
双向 | 查询 0x1018 各字段或 Node-ID |
4.2 LSS Address 直接对应对象 0x1018
源码定义:
1 | typedef union { |
这 4 个 uint32_t 就是 LSS 的 128 bit 地址。主站如果已知这四段值,可以直接发选择序列;如果不知道,就要靠 Fastscan 搜索。
这里有一个工程风险:0x1018 必须真正唯一。如果两个未配置设备的 Vendor-ID、Product Code、Revision Number、Serial Number 完全相同,LSS 主站无法可靠地区分它们。CANopenNode 的 LSS 教程也强调 LSS 地址依赖 0x1018 的唯一性。
4.3 LSS 状态只有 waiting 和 configuration
源码定义:
1 |
不要把 LSS 状态机和 NMT 状态机混在一起。LSS waiting/configuration 只决定 LSS 从站是否接受配置命令;NMT pre-operational/operational/stopped 决定的是普通 CANopen 通信服务可用性。
4.4 Node-ID 合法性宏体现了 LSS 的“未配置”语义
1 |
含义:
1..0x7F是有效 CANopen Node-ID。0xFF在 CANopenNode LSS 中表示“需要分配 Node-ID”。0不是有效普通节点 ID;广播 Node-ID 0 属于 NMT 等协议语义,不能分配给一个普通从站。
4.5 Bit timing 表把协议值映射成人能读的 kbit/s
CO_LSS_BIT_TIMING_* 里的表索引对应常见 CANopen 位率:1000、800、500、250、125、50、20、10 kbit/s,还有 CO_LSS_BIT_TIMING_AUTO。主站 API CO_LSSmaster_configureBitTiming() 接收的是数值 bit rate,再在函数里转换成 LSS 表索引。
5. 从站上电:为什么 LSS 要插在 CANopen 初始化中间
CO_LSSslave.h 的注释说明了一个关键启动顺序:
1 | CO_CANinit() |
原因是:LSS slave 和 NMT slave 需要共存,但如果节点没有有效 Node-ID,完整 CANopen 对象不能正常初始化。CANopenNode 的处理方式是:
- 应用先从 NVM、拨码开关或默认配置读出
pendingBitRate和pendingNodeID。 - 调用
CO_LSSslave_init(),把这两个变量的指针交给 LSS 从站对象。 - 如果
pendingNodeID是1..0x7F,它立刻成为activeNodeID,后续 NMT、SDO、PDO 等对象正常初始化。 - 如果
pendingNodeID == 0xFF,CANopenNode 只初始化并处理 LSS slave;其他 CANopen 模块会跳过或暂停。 - 当主站通过 LSS 配置出有效 Node-ID 后,从站在合适时机请求
CO_NMT_RESET_COMMUNICATION,重新走通信对象初始化流程。
这就是 LSS 比普通 SDO 更底层的原因:没有 Node-ID 时,SDO 不能作为配置入口;LSS 可以。
6. 从站对象 CO_LSSslave_t 的字段如何映射运行流程
CO_LSSslave_t 可以按四组理解。
6.1 身份和状态
1 | CO_LSS_address_t lssAddress; |
lssAddress是本设备真实 128 bit LSS 地址。lssState是 waiting/configuration。lssSelect暂存主站通过 0x40/0x41/0x42/0x43 发来的四段地址。lssFastscan和fastscanPos服务 Fastscan 搜索过程。
6.2 pending 值和 active 值
1 | uint16_t* pendingBitRate; |
这里是理解 LSS 的重点:
pendingNodeID是主站配置的新值,可能还没真正成为栈使用的 Node-ID。activeNodeID是当前 CAN 接口/通信对象正在使用的 Node-ID。- 对未配置节点,
activeNodeID == 0xFF,配置成功后通过 reset communication 让 pending 值生效。
6.3 接收缓存和发送标志
1 | volatile void* sendResponse; |
CAN RX 回调中不做复杂处理,只保存服务类型和数据,并设置 sendResponse。主循环或协议处理任务随后调用 CO_LSSslave_process() 处理。
6.4 应用回调
1 | bool_t (*pFunctLSScheckBitRate)(void* object, uint16_t bitRate); |
这三类回调是 LSS 从站把硬件能力检查、位率激活和参数持久化交给应用层处理的接口:
| 回调 | 触发命令 | 应用要做什么 |
|---|---|---|
pFunctLSScheckBitRate |
0x13 Configure Bit Timing |
判断目标 bit rate 是否被当前 CAN 外设时钟和位时序支持 |
pFunctLSSactivateBitRate |
0x15 Activate Bit Timing |
按延时要求停止发送、重配 CAN bit timing、恢复总线 |
pFunctLSScfgStore |
0x17 Store Configuration |
把 Node-ID 和 bit rate 写入 Flash/EEPROM/外部 NVM |
如果没有注册相关回调,CANopenNode 会用“不响应”或错误响应表达不支持。这一点对调试很重要:主站超时不一定是总线坏了,也可能是从站故意 no-ack 表示不支持该服务。
7. 从站接收路径:ISR 做短处理,主循环做协议动作
CO_LSSslave_receive() 是 CAN RX 回调,通常在中断或驱动接收上下文中调用。它的策略是:
- 只接受 DLC 为 8 的 LSS 帧。
- 如果已有待响应帧还没处理,就不覆盖当前请求。
- 对简单状态切换、选择地址、Fastscan 匹配做必要判断。
- 需要主循环进一步处理时,设置
service、复制CANdata、置位sendResponse。 - 如果启用了
CO_CONFIG_FLAG_CALLBACK_PRE,调用pFunctSignalPre()唤醒处理任务。
这种分工适合周期主循环或事件驱动任务。不要在 CAN RX 回调里写 Flash、重配 CAN、等待定时器,这些都应放到 CO_LSSslave_process() 或应用回调中。
8. 已知 LSS Address 时:选择指定从站
主站已知从站的 128 bit LSS Address 时,调用:
1 | CO_LSSmaster_swStateSelect(LSSmaster, timeDifference_us, &lssAddress); |
主站源码里的动作是连续发送四帧:
1 | 0x40 + Vendor-ID |
从站在 waiting 状态下逐段收集到 lssSelect。当收到最后一段 Serial Number 后,执行:
1 | CO_LSS_ADDRESS_EQUAL(LSSslave->lssAddress, LSSslave->lssSelect) |
如果完全匹配:
- 从站进入
CO_LSS_STATE_CONFIGURATION。 - 从站设置
service = CO_LSS_SWITCH_STATE_SEL_SERIAL。 - 后续
CO_LSSslave_process()发送0x44响应。 - 主站收到
0x44后认为选择成功。
如果不匹配,从站继续保持 waiting,不响应。对主站来说,这会表现为超时。
9. 配置 Node-ID:为什么配置后还要 reset communication
前置条件:从站已经处于 LSS configuration。通常是通过指定地址选择,或者 Fastscan 选中了一个未配置节点。
主站调用:
1 | CO_LSSmaster_configureNodeId(LSSmaster, timeDifference_us, nodeId); |
主站发送:
1 | data[0] = 0x11 // CO_LSS_CFG_NODE_ID |
从站处理逻辑:
- 检查
nodeId是否满足CO_LSS_NODE_ID_VALID()。 - 若有效,则写入
*pendingNodeID。 - 响应
0x11, errorCode=0。 - 若无效,则响应
0x11, errorCode=1或厂商错误。
关键点是:配置命令改变的是 pendingNodeID,不一定立刻改变 activeNodeID。如果设备之前是未配置状态,后续从 configuration 切回 waiting 时,CO_LSSslave_receive() 会检测到:
1 | activeNodeID == 0xFF |
于是让 CO_LSSslave_process() 返回 true,表示请求 CO_NMT_RESET_COMMUNICATION。这次通信复位后,新的 pending Node-ID 才会被完整 CANopen 栈采用。
CANopenNode 的 LSS 示例也提醒:修改 Node-ID 后,Node-ID 变化不是立即完成,通常要 reset communication/node 才会生效。
10. 保存配置:0x17 不是普通变量赋值,而是 NVM 边界
主站调用:
1 | CO_LSSmaster_configureStore(LSSmaster, timeDifference_us); |
从站收到 CO_LSS_CFG_STORE 后,如果注册了 pFunctLSScfgStore(),会调用应用层保存:
1 | pFunctLSScfgStore(object, pendingNodeID, pendingBitRate) |
这一步不是协议栈自己能替你完成的。不同工程的 NVM 后端可能是:
- MCU 内部 Flash 某个参数页。
- 外部 EEPROM。
- 外部 SPI NOR Flash。
- 上层配置文件或 Bootloader 参数区。
保存成功才应返回 OK。保存失败要返回错误,否则下次上电后主站以为配置已持久化,实际设备又回到旧 Node-ID 或旧 bit rate,排查会很困难。
11. 配置 bit rate:比配置 Node-ID 更危险
bit rate 的 LSS 流程分两步:
0x13 Configure Bit Timing:把新 bit rate 设置成 pending 值。0x15 Activate Bit Timing:让节点在指定延时后切换到 pending bit rate。
主站 API:
1 | CO_LSSmaster_configureBitTiming(LSSmaster, timeDifference_us, bit); |
源码中 configureBitTiming() 接收的 bit 是 1000/800/500/250/125/50/20/10/0 这类值,然后转换为 LSS 表索引。0 对应自动 bit rate 检测。
从站收到 0x13 后:
- 检查 table index 是否有效。
- 查表得到实际 kbit/s。
- 调
pFunctLSScheckBitRate()让应用确认当前硬件是否支持。 - 支持则写入
*pendingBitRate并响应 OK。
真正危险的是 0x15 Activate Bit Timing:CANopenNode 主站源码要求它只能在 CFG_GLOBAL 状态调用,因为切 bit rate 应该全网同时进行。官方文档也明确提示 bit rate 切换是关键步骤,失败会让网络不可用。
实现 pFunctLSSactivateBitRate() 时,关键约束是主站与所有目标从站必须按同一延时策略切换到同一 bit rate;否则后续 CAN 帧将无法互通。
12. Fastscan:不知道地址时如何找出一个未配置节点
Fastscan 的目标不是“一次列出所有设备”,而是:在一组未配置 LSS 从站中,识别出一个唯一的 LSS Address,并让这个从站进入 configuration 状态。 之后主站给它分配 Node-ID,再扫描下一个。
CANopenNode 主站入口:
1 | CO_LSSmaster_IdentifyFastscan(LSSmaster, timeDifference_us, &fastscan); |
fastscan 参数里每一段地址有三种策略:
1 | typedef enum { |
含义:
| 策略 | 含义 | 适用场景 |
|---|---|---|
FS_SCAN |
这 32 bit 完全扫描 | 完全未知 |
FS_MATCH |
已知该 32 bit,只验证 | 已知 Vendor-ID 或 Product Code |
FS_SKIP |
跳过该段 | 只允许有限跳过,不能随意跳过 Vendor-ID |
源码状态机注释很清楚:
1 | check for non-configured nodes |
12.1 “有响应”和“没响应”都携带信息
Fastscan 的一个不直观之处是:主站不是等从站返回具体位值,而是用“是否有人应答”来推断当前假设是否成立。
主站从 bit31 扫到 bit0。源码里当超时后如果没有收到 CO_LSS_IDENT_SLAVE,会把当前 bit 置 1:
1 | /* no response received, assumption is wrong */ |
简化理解:
- 主站先假设某些高位为 0,发请求。
- 如果仍有从站匹配,就会应答,说明假设可以保留。
- 如果无人应答,说明假设错了,把当前位改成 1 再继续。
这样逐位收敛出 32 bit,再换下一段地址。
12.2 从站只在未配置状态参与 Fastscan
CO_LSSslave_receive() 中对 Fastscan 有明确限制:
1 | pendingNodeID == 0xFF |
也就是说,CANopenNode 的 Fastscan 只用于未配置节点。已经有有效 Node-ID 的设备,通常可通过 SDO 读取 0x1018,或通过指定地址选择进入 LSS configuration。
13. 主站 API 为什么都要“循环调用”
CANopenNode 的 LSS master API 大多是非阻塞函数。典型模式是:
1 | CO_LSSmaster_return_t ret; |
第一次调用时:
- 检查状态机是否空闲。
- 填充
TXbuff->data[]。 - 调
CO_CANsend()发 0x7E5。 - 返回
CO_LSSmaster_WAIT_SLAVE。
后续调用时:
- 检查
CANrxNew是否收到响应。 - 若收到,解析
CANrxData[]。 - 若未收到,累计
timeoutTimer。 - 超时则返回
CO_LSSmaster_TIMEOUT或CO_LSSmaster_SCAN_NOACK。
这种设计适合周期主循环或事件驱动任务,不会在协议栈里阻塞等待 CAN 响应。
14. 主站返回值怎么读
CO_LSSmaster_return_t 里有三类返回值。
14.1 正常过程值
| 返回值 | 含义 |
|---|---|
CO_LSSmaster_WAIT_SLAVE |
请求已发出,还在等从站响应,下一周期继续调用同一个 API |
CO_LSSmaster_OK |
请求完成,且从站确认成功 |
CO_LSSmaster_SCAN_FINISHED |
Fastscan 某个扫描阶段完成或整个扫描成功 |
14.2 本地主站错误
| 返回值 | 常见原因 |
|---|---|
CO_LSSmaster_ILLEGAL_ARGUMENT |
传入 NULL、非法 Node-ID、非法 bit rate、Fastscan 参数不满足限制 |
CO_LSSmaster_INVALID_STATE |
上一个 LSS 命令还没完成,或当前主站状态不允许发该命令 |
CO_LSSmaster_TIMEOUT |
普通请求超时,无从站响应 |
CO_LSSmaster_SCAN_NOACK |
Fastscan 阶段没有符合条件的从站应答 |
CO_LSSmaster_SCAN_FAILED |
Fastscan 过程中收到错误响应或状态机无法继续 |
14.3 从站拒绝,但协议完成
| 返回值 | 含义 |
|---|---|
CO_LSSmaster_OK_ILLEGAL_ARGUMENT |
从站收到了请求,但拒绝该参数,比如不支持的 bit timing |
CO_LSSmaster_OK_MANUFACTURER |
从站返回厂商特定错误 |
这类返回值容易误判。它不是 CAN 总线失败,而是 LSS 从站有明确反馈:请求到了,但参数不被接受。
15. 主从机典型运行流程一:已知地址,修改 Node-ID
适用场景:设备有唯一 0x1018 身份对象,主站知道这四个字段。目标是把 Node-ID 改成 10 并保存。
1 | 1. Master: swStateSelect(vendor, product, revision, serial) |
对应 CANopenNode LSS demo 的典型命令形态是:先 lss_switch_sel,再 lss_get_node、lss_set_node、lss_store,最后切回 waiting 并 reset communication。
16. 主从机典型运行流程二:未知地址,Fastscan 后分配 Node-ID
适用场景:现场接入了一个或多个未配置节点,主站不知道它们的 0x1018 完整地址。
推荐流程:
1 | 1. Master: IdentifyFastscan() |
注意:Fastscan 的目标通常是一次选出一个节点。多节点批量配置时,不要尝试给多个未配置节点同时写同一个 Node-ID。
17. 主从机典型运行流程三:切换全网 bit rate
适用场景:所有节点当前能以同一 bit rate 通信,且都支持目标 bit rate。
最低风险流程:
1 | 1. 确认所有节点在线,并已知各自 LSS address 或能进入 LSS configuration |
不建议在以下情况下做全网 bit rate 激活:
- 有节点不确定是否支持目标位率。
- 主站自身切换 bit rate 的时序不可控。
- 总线上还有非 CANopen 或不可控设备。
- 没有现场恢复手段,一旦切错只能拆机单独重配。
18. 与 NMT 状态机的关系
LSS 和 NMT 是两套状态机,但会在启动和 reset communication 处交汇。
《CiA301 V4.2.0》中,NMT 初始化态包含初始化、复位应用、复位通信三个子状态;复位通信完成后执行 boot-up 写服务并进入配置态。配置态允许 SDO 通信但不允许 PDO 通信;运行态允许所有通信服务;停止态会终止大部分通信服务,只保留节点保护/心跳等错误控制服务。
对 LSS 学习来说,重点不是背 NMT 图,而是理解:
- LSS 可以先于完整 CANopen 通信对象工作。
- LSS 配置有效 Node-ID 后,通过 reset communication 让普通 CANopen 通信对象按新 Node-ID 初始化。
- 进入 NMT operational 后才是 PDO 等过程数据通信的正常阶段。
参考资料
- CANopenNode 官方 Doxygen:CANopen 305 / LSS / LSS Master / LSS Slave。
- CANopenNode GitHub:https://github.com/CANopenNode/CANopenNode,重点文件为
305/CO_LSS.h、305/CO_LSSmaster.c/h、305/CO_LSSslave.c/h。 - CiA:CiA 305 Layer Setting Services 公开介绍。
- 《CiA301 V4.2.0(中文注释版)》:用于 CANopen 设备模型、对象字典、NMT 状态机和通信对象上下文。



















