CANopen Emergency: 搞懂 EM 运行流程
@[toc]
先给结论
Emergency(下文简称 EMCY 或 EM)不是普通日志,也不是 PDO。它是 CANopen 用于故障事件上报的标准机制。CAN in Automation 对 EMCY 的公开说明包括:由设备内部错误触发、映射到单个 CAN Classic 帧、内容包含 0x1001 error register、16 位 emergency error code 和最多 5 字节制造商信息,默认 CAN-ID 为 0x80 + node-ID,且同一 error event 只发送一次。[^cia-emcy]
在 CANopenNode 的 301/CO_Emergency.c/h 中,运行链路可以压缩成:
1 | 应用/协议栈检测到错误变化 |
关键点:CO_errorReport() 不直接发 CAN 帧。 它只把错误变化写入内部状态和 FIFO;真正发帧发生在 CO_EM_process()。
1. 协议层:EMCY 解决什么问题
CANopen 中,PDO 负责过程数据,SDO 负责对象字典访问,NMT 负责节点状态控制,Heartbeat 负责在线监控。EMCY 的职责不同:它把设备内部错误、通信错误或应用错误,以事件方式通知网络中的其他节点。
CiA 公开说明中还强调:EMCY producer 发送后,零个或多个 EMCY consumer 可以接收这些消息,并执行应用相关的反应。也就是说,EMCY 本身只定义错误信息的上报通道;收到后要停机、报警、降级还是忽略,是设备或系统策略决定的。[^cia-emcy]
这解释了 CANopenNode 源码里的几个设计:
| 协议要求 | CANopenNode 源码对应 |
|---|---|
| 错误事件触发,不周期重复发送 | CO_error() 检查状态位是否变化;重复 report/reset 直接返回 |
| EMCY 是 8 字节 CAN 帧 | producer 初始化 TX buffer 时使用 DLC 8U |
默认 CAN-ID 为 0x80 + nodeId |
CO_CAN_ID_EMERGENCY + nodeId,由 0x1014 参与配置 |
| 帧中包含错误寄存器 | CO_EM_process() 计算 errorRegister 后写入 byte2 |
| 可保存错误历史 | CO_CONFIG_EM_HISTORY 使能 0x1003 读写扩展 |
| 可限制过密发送 | CO_CONFIG_EM_PROD_INHIBIT 使能 0x1015 抑制时间 |
2. 帧格式和相关 OD 对象
CANopenNode 官方 Doxygen 给出的 Emergency producer 消息内容为:bytes 0..1 是 error code,byte 2 是 error register,byte 3 是 error condition index,bytes 4..7 是 CO_errorReport() 的附加信息。[^canopennode-em]
| 字节 | 内容 | CANopenNode 来源 |
|---|---|---|
0..1 |
Emergency error code | CO_errorReport() 的 errorCode;reset 时变成 CO_EMC_NO_ERROR |
2 |
Error register | CO_EM_process() 根据 errorStatusBits[] 计算 |
3 |
Error condition index | CO_EM_errorStatusBits_t 中的 errorBit |
4..7 |
Additional information | CO_errorReport() 的 infoCode |
2.1 0x1001 Error register
0x1001 是 CANopen 的错误寄存器。CANopenNode 头文件把它描述为必需对象,CO_EM_init() 会通过 OD_getPtr() 取得 0x1001,00 的真实存储地址;之后 CO_EM_process() 直接写这个地址。
它不是每个错误的明细列表,而是错误类别汇总:generic、current、voltage、temperature、communication、device profile、manufacturer 等。CANopenNode 先把具体错误记录在 errorStatusBits[],再通过 CO_CONFIG_ERR_CONDITION_xxx 宏汇总到 0x1001。
2.2 0x1003 Pre-defined error field
如果启用 CO_CONFIG_EM_HISTORY,最新错误可以从对象字典 0x1003 读取;CANopenNode 官方说明指出 0x1003 内容对应 EMCY message 的 bytes 0..3。[^canopennode-em]
这意味着:
1 | 0x1003 保存:errorCode + errorRegister + errorBit |
源码上,0x1003 与 producer 发送共用 CO_EM_fifo_t。fifo.msg 保存 bytes 0..3,fifo.info 保存 producer 发帧用的 bytes 4..7。
2.3 0x1014 COB-ID EMCY
0x1014 决定 EMCY producer 使用的 COB-ID。若 CO_CONFIG_EM_PROD_CONFIGURABLE 未启用,CANopenNode 使用默认 CO_CAN_ID_EMERGENCY + nodeId;若启用,则 OD_write_1014() 会校验写入值,尤其避免 producer 已启用时随意切换正在使用的 CAN-ID。
2.4 0x1015 Inhibit time EMCY
0x1015 用于限制两帧 EMCY 的最小间隔。源码中 OD_write_1015() 把 OD 中的 UNSIGNED16 数值按 100 us 单位换算为微秒:
1 | em->inhibitEmTime_us = (uint32_t)CO_getUint16(buf) * 100U; |
因此,0x1015 = 10 表示 1000 us,即 1 ms。
3. 源码文件分工
建议先读 CO_Emergency.h,再读 CO_Emergency.c:
1 | CO_Emergency.h |
CO_EM_t 是运行态中心结构体,可以按功能拆成:
| 字段 | 作用 |
|---|---|
errorStatusBits[] |
当前内部错误位图,一个错误条件对应一个 bit |
errorRegister |
指向 OD 0x1001,00 的指针 |
CANerrorStatusOld |
保存上次 CAN driver 错误状态,用于检测变化 |
fifo/fifoWrPtr/fifoPpPtr/fifoCount/fifoOverflow |
环形 FIFO,用于 producer 发送和 0x1003 历史 |
producerEnabled/nodeId/CANtxBuff/inhibitEmTimer |
producer 发送状态 |
OD_1014_extension/OD_1015_extension/OD_1003_extension/OD_statusBits_extension |
OD 动态访问扩展 |
pFunctSignalRx/pFunctSignalPre |
consumer 回调和 pre callback |
4. CO_EM_init():初始化时把 OD、FIFO 和 CAN buffer 接起来
你贴的初始化调用本质上是在把 CiA 301 EMCY 需要的对象连接到 CANopenNode 运行时:
1 | err = CO_EM_init(co->em, co->CANmodule, OD_GET(H1001, OD_H1001_ERR_REG), |
逐项看:
| 初始化参数 | 含义 | 影响 |
|---|---|---|
co->em |
Emergency 对象实例 | 保存运行态:状态位、FIFO、OD 扩展、TX/RX 回调 |
co->CANmodule |
CAN 模块 | 用于 TX,也用于读取 CANerrorStatus |
OD_GET(H1001, ...) |
0x1001 Error register |
CO_EM_process() 计算后写入 |
co->em_fifo |
EM FIFO | producer 发送队列和 0x1003 历史共用 |
CO_GET_CNT(ARR_1003) + 1U |
FIFO 数组大小 | 实际可保存历史条数是 ARR_1003 |
OD_GET(H1014, ...) |
0x1014 COB-ID EMCY |
决定 producer CAN-ID 和 enabled 状态 |
CO_GET_CO(TX_IDX_EM_PROD) |
TX buffer index | EMCY producer 使用的 CAN TX buffer |
OD_GET(H1015, ...) |
0x1015 Inhibit time |
控制 EMCY 最小发送间隔 |
OD_GET(H1003, ...) |
0x1003 Pre-defined error field |
挂接错误历史读写扩展 |
OD_statusBits |
自定义内部状态位 OD 入口 | 仅在 CO_CONFIG_EM_STATUS_BITS 打开时出现 |
CO_GET_CO(RX_IDX_EM_CONS) |
consumer RX buffer index | 接收其他节点默认范围 EMCY |
nodeId |
当前节点号 | 默认 EMCY CAN-ID = 0x80 + nodeId |
4.1 为什么 em_fifo 是 ARR_1003 + 1
环形 FIFO 需要保留一个空槽来区分“空”和“满”:
1 | fifoWrPtr == fifoPpPtr -> 空 |
所以:
1 | fifoSize = 0x1003 历史容量 + 1 |
CO_Emergency.c 文件顶部也用 fifoSize = 7、实际容量 6 的图示说明了这个关系。
5. CO_error():错误变化如何进入内部状态和 FIFO
CO_errorReport() 和 CO_errorReset() 都是宏,最终进入 CO_error():
1 |
5.1 errorBit 转成数组下标和位掩码
1 | uint8_t index = errorBit >> 3; |
例如 errorBit = 0x12:
1 | index = 0x12 >> 3 = 2 |
5.2 重复调用不会重复入队
源码先判断状态是否变化:
1 | if (setError) { |
效果如下:
| 调用 | 当前 bit | 结果 |
|---|---|---|
| report | 1 | 直接返回 |
| report | 0 | 置位并写 FIFO |
| reset | 0 | 直接返回 |
| reset | 1 | 清位并写 FIFO,error code 改成 0x0000 |
这与 EMCY “同一 error event 只发送一次”的协议原则一致。
5.3 CO_error() 先留空 error register
CO_error() 准备的 errMsg 是:
1 | uint32_t errMsg = ((uint32_t)errorBit << 24) | CO_SWAP_16(errorCode); |
此时只放入:
1 | byte0..1 = errorCode |
byte2 不在这里填,是因为 error register 需要综合全部 errorStatusBits[] 和 CO_CONFIG_ERR_CONDITION_xxx 宏计算。这个工作放在周期性的 CO_EM_process() 更合适。
6. CO_EM_process():计算 0x1001 并发送 EMCY
CANopenNode 官方 Doxygen 说明 CO_EM_process() 必须周期调用,它会检查部分通信错误、计算 OD 0x1001,并在必要时发送 EMCY。[^canopennode-em]
源码顺序是:
- 检查 CAN driver 的错误状态变化。
- 根据
errorStatusBits[]计算errorRegister,写入*em->errorRegister。 - 若当前上下文不允许发送,则返回。
- 若 producer、FIFO、TX buffer、inhibit time 条件满足,则发送一帧 EMCY。
6.1 CAN driver 错误也会变成 EM 输入
CO_EM_process() 读取:
1 | uint16_t CANerrSt = em->CANdevTx->CANerrorStatus; |
如果与 CANerrorStatusOld 不同,就把变化转换成 CO_error() 调用。例如:
| CAN driver 状态 | CANopenNode errorBit | errorCode |
|---|---|---|
| TX/RX warning | CO_EM_CAN_BUS_WARNING |
CO_EMC_NO_ERROR |
| TX passive | CO_EM_CAN_TX_BUS_PASSIVE |
CO_EMC_CAN_PASSIVE |
| TX bus off | CO_EM_CAN_TX_BUS_OFF |
CO_EMC_BUS_OFF_RECOVERED |
| TX overflow | CO_EM_CAN_TX_OVERFLOW |
CO_EMC_CAN_OVERRUN |
| RX overflow | CO_EM_CAN_RXB_OVERFLOW |
CO_EMC_CAN_OVERRUN |
6.2 0x1001 是由条件宏汇总的
默认配置中,CANopenNode 预定义了三类条件:
1 |
这就是 errorStatusBits[] 与 0x1001 的关系:前者是细粒度内部状态,后者是 CANopen 标准对象中对错误类别的汇总。
6.3 发送前补齐 byte2
producer 分支中,满足条件后源码补齐 byte2:
1 | em->fifo[fifoPpPtr].msg |= (uint32_t)errorRegister << 16; |
然后复制 8 字节并发送:
1 | (void)memcpy((void*)em->CANtxBuff->data, (void*)&em->fifo[fifoPpPtr].msg, sizeof(em->CANtxBuff->data)); |
CO_EM_fifo_t 的字段顺序是 msg 后接 info,所以这次 8 字节复制会把 msg 和 info 一起送入 CAN TX buffer。
7. FIFO 与 0x1003:事件队列和历史读取共用一套存储
errorStatusBits[] 保存当前状态;FIFO 保存状态变化事件。例如:
1 | 过压出现 -> report -> FIFO 加一条错误事件 |
OD_read_1003() 中,subindex 0 返回 fifoCount,subindex 1 返回最新错误。它从 fifoWrPtr 往回数,因此 0x1003:01 是最新一条,0x1003:02 是次新一条。
写 0x1003:00 = 0 会清空错误历史计数:
1 | if (CO_getUint8(buf) != 0U) { |
它清的是历史读取计数,不是直接清 errorStatusBits[] 当前状态。
8. CO_CONFIG_EM:配置宏影响哪些源码路径
CANopenNode 官方配置文档列出 CO_CONFIG_EM 的可选 flag:producer、producer COB-ID configurable、producer inhibit、history、consumer、status bits、callback pre、timer next。[^canopennode-config]
| 配置 | 作用 | 主要源码路径 |
|---|---|---|
CO_CONFIG_EM_PRODUCER |
启用本节点 EMCY 发送 | CO_EM_init() 初始化 0x1014 和 TX buffer;CO_EM_process() 发送 |
CO_CONFIG_EM_PROD_CONFIGURABLE |
允许运行期配置 producer COB-ID | OD_read_1014() / OD_write_1014() |
CO_CONFIG_EM_PROD_INHIBIT |
启用发送抑制时间 | OD_write_1015()、inhibitEmTimer |
CO_CONFIG_EM_HISTORY |
启用错误历史 | OD_read_1003() / OD_write_1003() |
CO_CONFIG_EM_CONSUMER |
接收其他节点 EMCY | CO_EM_receive()、CO_EM_initCallbackRx() |
CO_CONFIG_EM_STATUS_BITS |
通过 OD 访问内部 errorStatusBits[] |
OD_read_statusBits() / OD_write_statusBits() |
CO_CONFIG_FLAG_CALLBACK_PRE |
错误变化后触发回调 | CO_EM_initCallbackPre()、CO_error() 末尾回调 |
CO_CONFIG_FLAG_TIMERNEXT |
计算下次处理时间 | inhibit 分支更新 timerNext_us |
9. CO_CONFIG_EM_STATUS_BITS:补充说明
CO_CONFIG_EM_STATUS_BITS 容易被忽略,因为它不影响 EMCY 帧格式,也不是 CiA 301 标准的固定对象号。CANopenNode 官方配置文档对它的定义是:从 OD 访问 Error status bits。[^canopennode-config]
它的作用可以概括为:把 CO_EM_t.errorStatusBits[] 暴露给一个自定义 OD 项,供 SDO/调试工具/厂商扩展读取或写入内部错误位图。
9.1 初始化时需要 OD_statusBits
头文件的 CO_EM_init() 参数说明写明:OD_statusBits 是用于访问 CO_EM_t 中 errorStatusBits 的自定义 OD entry;这个 entry 需要在 subindex 0 上提供 (CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8) 字节变量,并要求 IO extension。源码在 CO_CONFIG_EM_STATUS_BITS 打开时才会展开这个参数。
对应初始化分支:
1 |
|
9.2 读路径:读取内部错误位图
OD_read_statusBits() 的核心行为是把内部状态位复制出去:
1 | OD_size_t countReadLocal = CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8U; |
因此,如果你通过 SDO 读取这个自定义 OD 项,读到的是当前 errorStatusBits[] 的原始位图。它比 0x1001 更细,但也更偏 CANopenNode 内部实现。
9.3 写路径:可直接改内部错误位图
OD_write_statusBits() 的核心行为是反向复制:
1 | OD_size_t countWrite = CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8U; |
这说明它不是只读诊断入口,而是可写入口。写入后,下一次 CO_EM_process() 会基于新的 errorStatusBits[] 重新计算 0x1001。但要注意:直接写 errorStatusBits[] 不会像 CO_errorReport() 那样自动生成 FIFO 事件,也不会自动形成一帧 EMCY。需要事件上报时,应用仍应调用 CO_errorReport() / CO_errorReset()。
9.4 它和 0x1001、0x1003 的区别
| 对象/入口 | 内容 | 是否标准固定对象 | 是否触发 EMCY |
|---|---|---|---|
0x1001 |
错误类别汇总 | 是 | 否,只是当前状态输出 |
0x1003 |
最近错误历史 bytes 0..3 |
是 | 否,只是历史读取 |
OD_statusBits |
内部 errorStatusBits[] 原始位图 |
否,由工程自定义 | 否,直接读写不产生事件 |
CO_errorReport() |
错误发生事件输入 | API | 是,入 FIFO 后由 CO_EM_process() 发送 |
9.5 什么时候需要打开
建议按用途决定:
| 场景 | 建议 |
|---|---|
| 只学习 EMCY producer/history 主流程 | 可不打开,避免混淆 |
需要通过 SDO 观察内部每个 CO_EM_xxx bit |
打开,并在 OD 中放一个自定义 byte array |
需要调试 CO_CONFIG_ERR_CONDITION_xxx 为什么使 0x1001 置位 |
打开有帮助 |
| 希望主站通过 OD 强行清/置内部错误位 | 可以打开,但要明确这不会自动生成 EMCY 事件 |
工程上更推荐:应用错误的正常生命周期仍用 CO_errorReport() / CO_errorReset();OD_statusBits 主要作为调试和诊断入口。
10. 从机工程中的最小使用路径
应用层上报自定义错误时,优先使用 manufacturer 范围的 errorBit:
1 |
|
调试时按这个顺序检查:
1 | 1. CAN 总线上是否出现 0x80 + Node-ID 的 8 字节帧 |
最小移植检查表:
| 检查项 | 预期 |
|---|---|
CO_GET_CNT(EM) == 1 |
工程里确实有 Emergency 对象 |
0x1001 |
存在,UNSIGNED8,可被 EM 模块绑定 |
0x1003 |
启用 history 时存在,容量和 ARR_1003 一致 |
0x1014 |
启用 producer 时存在,默认值能得到 0x80 + nodeId |
0x1015 |
启用 inhibit 时存在,单位是 100 us |
OD_statusBits |
仅启用 CO_CONFIG_EM_STATUS_BITS 时需要自定义 OD entry |
CO_EM_process() |
在 CANopen 主循环中周期调用 |
| TX buffer index | TX_IDX_EM_PROD 不与其他对象冲突 |
11. 参考资料
[^cia-emcy]: CAN in Automation (CiA), “Special function protocols”,Emergency protocol 说明 EMCY 用于通知设备内部错误、映射到单个 CAN CC 帧、默认 CAN-ID 为 80h + node-ID,且每个 error event 只发送一次。https://www.can-cia.org/can-knowledge/special-function-protocols
[^canopennode-em]: CANopenNode 官方 Doxygen,“Emergency”,说明 CO_EM_init()、CO_EM_process()、CO_error() 以及 Emergency producer message bytes 布局和 0x1003 历史。https://canopennode.github.io/CANopenNode/group__CO__Emergency.html
[^canopennode-config]: CANopenNode 官方 Doxygen,“Emergency producer/consumer”,说明 CO_CONFIG_EM 的 producer、configurable、inhibit、history、consumer、status bits、callback pre、timer next 等配置,以及 CO_CONFIG_EM_ERR_STATUS_BITS_COUNT 默认值和范围。https://canopennode.github.io/CANopenNode/group__CO__STACK__CONFIG__EMERGENCY.html
[^canopennode-repo]: CANopenNode GitHub README 说明 CANopenNode 可以运行在不同设备或微控制器上,具体设备接口通常在独立工程中维护。https://github.com/CANopenNode/CANopenNode


















