按 LED 源码学习 CiA 303-3:CANopenNode 如何计算 RUN / ERROR 指示灯
@[toc]
结论
303/CO_LEDs.c/h 的核心不是“驱动 LED”,而是实现 CiA 303-3 CANopen indicators:把 CANopen 设备的通信相关状态,转换成标准的 绿色 RUN LED 与 红色 ERROR LED 指示模式。
本模块的输入来自 CANopen 状态与错误上下文,例如 NMT 状态、LSS 配置状态、CAN bus-off、CAN warning、Heartbeat consumer 错误、SYNC 超时、RPDO event timer 超时、其他错误、固件下载状态等;输出是 LEDred / LEDgreen bitfield 中的 CO_LED_CANopen 位。
本文只讨论 CANopenNode 经典 CANopen 的:
303/CO_LEDs.h303/CO_LEDs.c
NMT、LSS、Heartbeat consumer、SYNC、PDO、CAN bus-off 只作为 LED 输入上下文出现,不展开这些协议模块本身。
1. 先理解 CiA 303-3:它规定的是“通信状态怎么被看见”
CiA 303 是 CANopen 相关设备与网络设计建议的一组文档。CiA 官方资料说明,CiA 303-3 是 indicator specification,用于描述 CANopen 设备上的通信相关指示器;额外的应用相关指示器应由设备 profile 或制造商自行定义。官方说明还指出,303-3 推荐用 LED 闪烁模式指示设备状态,帮助服务人员在没有完整诊断工具时识别通信问题。[^cia303]
所以,CiA 303-3 的重点不是:
1 | 怎么配置 NMT? |
而是:
1 | 当这些 CANopen 通信状态已经产生之后,设备应该用什么 LED 模式表达出来? |
CANopenNode 的 Doxygen 明确把 CO_LEDs.h 标为 CANopen Indicator specification (CiA 303-3 v1.4.0),并说明 CiA 303-3 使用绿色 RUN LED、红色 ERROR LED 或红绿双色 LED 来反映 CANopen 设备状态。[^coleds]
2. 协议层面的两类指示灯
2.1 RUN 绿灯:回答“设备运行到哪一步了”
CANopenNode 对 RUN LED 的语义列在 CO_LEDs.h 注释和 Doxygen 中:[^coleds]
| RUN LED 模式 | 表示的 CANopen 状态 |
|---|---|
flickering |
LSS configuration state is active |
blinking |
device is in NMT pre-operational state |
single flash |
device is in NMT stopped state |
triple flash |
software download is running in the device |
on |
device is in NMT operational state |
这里的 RUN LED 不是“程序有没有运行”的通用心跳灯,而是 CANopen 通信状态指示灯。它重点表达设备处于配置、预操作、停止、运行或固件下载等状态。
2.2 ERROR 红灯:回答“通信/配置是否异常”
ERROR LED 的语义同样来自 CO_LEDs.h 注释和 Doxygen:[^coleds]
| ERROR LED 模式 | 表示的 CANopen 错误状态 |
|---|---|
off |
no error |
flickering |
LSS node ID is not configured, CANopen is not initialized |
blinking |
invalid configuration, general error |
single flash |
CAN warning limit reached |
double flash |
heartbeat consumer error in remote monitored node |
triple flash |
sync message reception timeout |
quadruple flash |
PDO has not been received before the event timer elapsed |
on |
CAN bus off |
ERROR LED 不是错误日志,也不是错误队列。它在某一时刻只能显示一个最终模式。多个错误同时存在时,CANopenNode 在 CO_LEDs.c 里用固定优先级选择最终显示内容。
3. CANopenNode 为什么把它放在 303/ 目录
CANopenNode 仓库结构中,301/ 是 CANopen application layer and communication profile,里面有 NMT、Heartbeat、Emergency、PDO、SYNC 等模块;303/ 被标为 CANopen Recommendation,并包含 CO_LEDs.h/.c - CANopen LED Indicators。[^repo]
这说明 CO_LEDs 的定位不是 CAN 收发驱动,也不是 NMT 状态机本体,而是 CANopen recommendation 层的显示规则实现:
1 | 301/ 模块产生状态或错误上下文 |
4. 从 CO_LEDs.h 开始读:头文件先给出协议语义
CO_LEDs.h 里最重要的内容有三类。
4.1 模式 bitmask
1 |
这些宏分成两层:
| 宏 | 作用 |
|---|---|
CO_LED_flicker / CO_LED_blink / CO_LED_flash_1..4 |
表示某种闪烁模式当前相位是否为 ON |
CO_LED_CANopen |
表示 CANopen 指示灯最终输出是否为 ON |
也就是说,CO_LED_flash_3 不是“错误类型”,而是“三闪模式在当前时间片是否点亮”的节拍位;CO_LED_CANopen 才是应用层最终要读取的 CANopen LED 开关结果。
4.2 输出读取宏
1 |
对 CANopen 指示灯而言,读取方式是:
1 | CO_LED_RED(LEDs, CO_LED_CANopen) |
这里仍然只是在读 CO_LEDs_t 内部计算结果,不涉及具体板级输出。
4.3 CO_LEDs_t 保存时基、模式相位和输出结果
1 | typedef struct { |
这个结构体不是只保存两个 LED 的开关状态,而是同时保存:
LEDtmr50ms:50 ms 基准时钟累计值;LEDtmr200ms:200 ms 模式时间槽计数;LEDtmrflash_1..4:单闪、双闪、三闪、四闪的状态机计数;LEDred/LEDgreen:红绿两路 bitfield,里面既有模式相位位,也有最终CO_LED_CANopen输出位。
5. 再读 CO_LEDs.c:源码把协议模式拆成“时基 + 优先级 + 输出位”
CO_LEDs.c 只有两个公开函数:
1 | CO_ReturnError_t CO_LEDs_init(CO_LEDs_t* LEDs); |
Doxygen 说明 CO_LEDs_process() 必须周期调用,并且参数中包含 NMT operating state、LSS configuration indication、CAN bus-off、CAN warning、RPDO timeout、SYNC timeout、Heartbeat consumer error、other error、firmware download 等输入。[^coleds]
因此,它的运行逻辑可以分成三层。
5.1 第一层:累计时间,只在 50 ms tick 上更新
源码先做:
1 | LEDs->LEDtmr50ms += timeDifference_us; |
含义是:CO_LEDs_process() 可以被更高频调用,但 LED 模式本身以 50 ms 为基本离散时间片更新。
5.2 第二层:生成 flicker / blink / flash 的当前相位
每个 50 ms tick 都会更新 flicker 相位;每累计 4 个 50 ms tick,即 200 ms,再更新 blink 和 flash_1..4。
CO_LEDs.h 给出的模式位含义是:
| 模式位 | 频率/形态 |
|---|---|
CO_LED_flicker |
10 Hz flickering |
CO_LED_blink |
2.5 Hz blinking |
CO_LED_flash_1 |
single flash |
CO_LED_flash_2 |
double flash |
CO_LED_flash_3 |
triple flash |
CO_LED_flash_4 |
quadruple flash |
源码内部用 rd 和 gr 两个临时 bitfield 生成红、绿两套相位。后面 ERROR LED 只取 rd 中对应模式位,RUN LED 只取 gr 中对应模式位。
这一步还没有决定“当前是什么 CANopen 状态”,只是准备好各种标准模式在当前时刻的 ON/OFF 相位。
5.3 第三层:用输入状态选择最终 CANopen 输出位
当 tick == true 时,源码开始计算:
1 | uint8_t rd_co, gr_co; |
其中:
rd_co:红色 ERROR LED 在当前时刻是否点亮;gr_co:绿色 RUN LED 在当前时刻是否点亮。
最后再写回:
1 | if (rd_co != 0U) { |
所以最终输出的关键不是 rd_co / gr_co 变量本身,而是 LEDred / LEDgreen 里的 CO_LED_CANopen bit。
6. ERROR 红灯:协议错误语义在源码中的优先级
CO_LEDs.c 对 ERROR LED 的判断顺序如下:
1 | if (ErrCANbusOff) { |
对应关系如下:
| 优先级 | 输入条件 | ERROR LED 模式 | 语义 |
|---|---|---|---|
| 1 | ErrCANbusOff |
on |
CAN bus off |
| 2 | NMTstate == CO_NMT_INITIALIZING |
flickering |
CANopen 未初始化 / Node-ID 未配置语义 |
| 3 | ErrRpdo |
quadruple flash |
RPDO event timer timeout |
| 4 | ErrSync |
triple flash |
SYNC receive timeout |
| 5 | ErrHbCons |
double flash |
Heartbeat consumer error |
| 6 | ErrCANbusWarn |
single flash |
CAN warning limit reached |
| 7 | ErrOther |
blinking |
invalid configuration / general error |
| 8 | 无以上条件 | off |
no error |
这里要注意两点:
- 表格中的协议语义不是多个 LED 同时叠加显示。 由于源码使用
if / else-if,同一时刻只选择一个 ERROR 模式。 ErrCANbusOff是最高优先级。 一旦 bus-off 为真,红灯直接常亮,不再显示其他错误模式。
7. RUN 绿灯:运行状态在源码中的优先级
RUN LED 的源码判断顺序如下:
1 | if (LSSconfig) { |
对应关系如下:
| 优先级 | 输入条件 | RUN LED 模式 | 语义 |
|---|---|---|---|
| 1 | LSSconfig |
flickering |
LSS configuration state is active |
| 2 | firmwareDownload |
triple flash |
software download is running |
| 3 | NMTstate == CO_NMT_STOPPED |
single flash |
NMT stopped |
| 4 | NMTstate == CO_NMT_PRE_OPERATIONAL |
blinking |
NMT pre-operational |
| 5 | NMTstate == CO_NMT_OPERATIONAL |
on |
NMT operational |
| 6 | 其他状态 | off |
不显示 RUN 状态 |
从这个顺序可以看出,RUN LED 不是单纯显示 NMT 状态。LSSconfig 和 firmwareDownload 会优先覆盖 NMT 状态显示。
8. 把完整流程串起来
CO_LEDs_process() 每次被调用时,逻辑可以概括为:
1 | 输入: |
最终,CANopen 指示灯的开关状态由以下两个表达式读取:
1 | CO_LED_RED(LEDs, CO_LED_CANopen) |
这就是 303/CO_LEDs.c/h 的核心:
先按 CiA 303-3 的模式定义生成标准闪烁相位,再按 CANopen 状态与错误输入选择当前应显示的 RUN / ERROR 语义,最后写成红绿 LED 的
CO_LED_CANopen输出位。
9. 学习这份源码时的主线
建议按下面顺序理解,而不是一开始就陷入定时器细节:
- 先看 CiA 303-3 要表达什么。 绿色 RUN 表示运行/配置状态,红色 ERROR 表示错误/异常状态。
- 再看
CO_LEDs.h。 它把协议里的 flicker、blink、single flash、double flash、triple flash、quadruple flash、CANopen 输出位定义成 bitmask。 - 再看
CO_LEDs_t。 它不是两个 bool,而是保存 50 ms/200 ms 计时器、flash 状态机和红绿 bitfield。 - 最后看
CO_LEDs_process()。 它先生成模式相位,再按 ERROR 与 RUN 两条优先级链选择最终输出。
只要抓住这条主线,CO_LEDs.c 就不再是零散的闪灯代码,而是 CiA 303-3 指示语义在 CANopenNode 里的一个小型状态显示器。
参考资料
[^cia303]: CAN in Automation, “CiA® 303 series: CANopen-related documents”。该页面说明 CiA 303-3 是 CANopen indicators,并描述其用于通信相关指示器和 LED 闪烁模式。https://www.can-cia.org/can-knowledge/cia-303-series-canopen-related-documents
[^coleds]: CANopenNode Doxygen, “LED indicators” 与 “303/CO_LEDs.h File Reference”。该文档说明 CO_LEDs.h 是 CANopen Indicator specification (CiA 303-3 v1.4.0),列出 RUN/ERROR LED 模式,并给出 CO_LEDs_process() 输入参数。https://canopennode.github.io/CANopenNode/group__CO__LEDs.html
[^repo]: CANopenNode GitHub repository README, “File structure”。该仓库结构说明 303/ - CANopen Recommendation 下包含 CO_LEDs.h/.c - CANopen LED Indicators。https://github.com/CANopenNode/CANopenNode















