CANopen Emergency: 搞懂 EM 运行流程
CANopen Emergency 搞懂 EM 运行流程文章目录CANopen Emergency 搞懂 EM 运行流程先给结论1. 协议层EMCY 解决什么问题2. 帧格式和相关 OD 对象2.1 0x1001 Error register2.2 0x1003 Pre-defined error field2.3 0x1014 COB-ID EMCY2.4 0x1015 Inhibit time EMCY3. 源码文件分工4. CO_EM_init()初始化时把 OD、FIFO 和 CAN buffer 接起来4.1 为什么 em_fifo 是 ARR_1003 15. CO_error()错误变化如何进入内部状态和 FIFO5.1 errorBit 转成数组下标和位掩码5.2 重复调用不会重复入队5.3 CO_error() 先留空 error register6. CO_EM_process()计算 0x1001 并发送 EMCY6.1 CAN driver 错误也会变成 EM 输入6.2 0x1001 是由条件宏汇总的6.3 发送前补齐 byte27. FIFO 与 0x1003事件队列和历史读取共用一套存储8. CO_CONFIG_EM配置宏影响哪些源码路径9. CO_CONFIG_EM_STATUS_BITS补充说明9.1 初始化时需要 OD_statusBits9.2 读路径读取内部错误位图9.3 写路径可直接改内部错误位图9.4 它和 0x1001、0x1003 的区别9.5 什么时候需要打开10. 从机工程中的最小使用路径11. 参考资料先给结论Emergency下文简称EMCY或EM不是普通日志也不是 PDO。它是 CANopen 用于故障事件上报的标准机制。CAN in Automation 对 EMCY 的公开说明包括由设备内部错误触发、映射到单个 CAN Classic 帧、内容包含0x1001error register、16 位 emergency error code 和最多 5 字节制造商信息默认 CAN-ID 为0x80 node-ID且同一 error event 只发送一次。1在 CANopenNode 的301/CO_Emergency.c/h中运行链路可以压缩成应用/协议栈检测到错误变化 - CO_errorReport() / CO_errorReset() - CO_error() 修改 errorStatusBits[] 并写入 FIFO - CO_EM_process() 周期运行 - 计算 OD 0x1001 Error register - 补齐 EMCY byte2 并按 NMT/TX/inhibit 条件发送 CAN 帧 - OD 0x1003 可读取最近错误历史关键点CO_errorReport()不直接发 CAN 帧。它只把错误变化写入内部状态和 FIFO真正发帧发生在CO_EM_process()。1. 协议层EMCY 解决什么问题CANopen 中PDO 负责过程数据SDO 负责对象字典访问NMT 负责节点状态控制Heartbeat 负责在线监控。EMCY 的职责不同它把设备内部错误、通信错误或应用错误以事件方式通知网络中的其他节点。CiA 公开说明中还强调EMCY producer 发送后零个或多个 EMCY consumer 可以接收这些消息并执行应用相关的反应。也就是说EMCY 本身只定义错误信息的上报通道收到后要停机、报警、降级还是忽略是设备或系统策略决定的。1这解释了 CANopenNode 源码里的几个设计协议要求CANopenNode 源码对应错误事件触发不周期重复发送CO_error()检查状态位是否变化重复 report/reset 直接返回EMCY 是 8 字节 CAN 帧producer 初始化 TX buffer 时使用 DLC8U默认 CAN-ID 为0x80 nodeIdCO_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 消息内容为bytes0..1是 error codebyte2是 error registerbyte3是 error condition indexbytes4..7是CO_errorReport()的附加信息。2字节内容CANopenNode 来源0..1Emergency error codeCO_errorReport()的errorCodereset 时变成CO_EMC_NO_ERROR2Error registerCO_EM_process()根据errorStatusBits[]计算3Error condition indexCO_EM_errorStatusBits_t中的errorBit4..7Additional informationCO_errorReport()的infoCode2.10x1001 Error register0x1001是 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.20x1003 Pre-defined error field如果启用CO_CONFIG_EM_HISTORY最新错误可以从对象字典0x1003读取CANopenNode 官方说明指出0x1003内容对应 EMCY message 的 bytes0..3。2这意味着0x1003 保存errorCode errorRegister errorBit 0x1003 不保存infoCode也就是 EMCY bytes 4..7源码上0x1003与 producer 发送共用CO_EM_fifo_t。fifo.msg保存 bytes0..3fifo.info保存 producer 发帧用的 bytes4..7。2.30x1014 COB-ID EMCY0x1014决定 EMCY producer 使用的 COB-ID。若CO_CONFIG_EM_PROD_CONFIGURABLE未启用CANopenNode 使用默认CO_CAN_ID_EMERGENCY nodeId若启用则OD_write_1014()会校验写入值尤其避免 producer 已启用时随意切换正在使用的 CAN-ID。2.40x1015 Inhibit time EMCY0x1015用于限制两帧 EMCY 的最小间隔。源码中OD_write_1015()把 OD 中的UNSIGNED16数值按100 us单位换算为微秒em-inhibitEmTime_us(uint32_t)CO_getUint16(buf)*100U;em-inhibitEmTimer0;因此0x1015 10表示1000 us即1 ms。3. 源码文件分工建议先读CO_Emergency.h再读CO_Emergency.cCO_Emergency.h - 默认配置宏 - CO_errorRegister_t - CO_EM_errorCode_t - CO_EM_errorStatusBits_t - CO_EM_fifo_t - CO_EM_t - CO_EM_init / CO_error / CO_EM_process 声明 CO_Emergency.c - OD 读写扩展函数 - CO_EM_init() - CO_EM_receive() / callback - CO_EM_process() - CO_error()CO_EM_t是运行态中心结构体可以按功能拆成字段作用errorStatusBits[]当前内部错误位图一个错误条件对应一个 biterrorRegister指向 OD0x1001,00的指针CANerrorStatusOld保存上次 CAN driver 错误状态用于检测变化fifo/fifoWrPtr/fifoPpPtr/fifoCount/fifoOverflow环形 FIFO用于 producer 发送和0x1003历史producerEnabled/nodeId/CANtxBuff/inhibitEmTimerproducer 发送状态OD_1014_extension/OD_1015_extension/OD_1003_extension/OD_statusBits_extensionOD 动态访问扩展pFunctSignalRx/pFunctSignalPreconsumer 回调和 pre callback4.CO_EM_init()初始化时把 OD、FIFO 和 CAN buffer 接起来你贴的初始化调用本质上是在把 CiA 301 EMCY 需要的对象连接到 CANopenNode 运行时errCO_EM_init(co-em,co-CANmodule,OD_GET(H1001,OD_H1001_ERR_REG),#if((CO_CONFIG_EM)(CO_CONFIG_EM_PRODUCER|CO_CONFIG_EM_HISTORY))!0co-em_fifo,(CO_GET_CNT(ARR_1003)1U),#endif#if((CO_CONFIG_EM)CO_CONFIG_EM_PRODUCER)!0OD_GET(H1014,OD_H1014_COBID_EMERGENCY),CO_GET_CO(TX_IDX_EM_PROD),#if((CO_CONFIG_EM)CO_CONFIG_EM_PROD_INHIBIT)!0OD_GET(H1015,OD_H1015_INHIBIT_TIME_EMCY),#endif#endif#if((CO_CONFIG_EM)CO_CONFIG_EM_HISTORY)!0OD_GET(H1003,OD_H1003_PREDEF_ERR_FIELD),#endif#if((CO_CONFIG_EM)CO_CONFIG_EM_STATUS_BITS)!0OD_statusBits,#endif#if((CO_CONFIG_EM)CO_CONFIG_EM_CONSUMER)!0co-CANmodule,CO_GET_CO(RX_IDX_EM_CONS),#endifnodeId,errInfo);逐项看初始化参数含义影响co-emEmergency 对象实例保存运行态状态位、FIFO、OD 扩展、TX/RX 回调co-CANmoduleCAN 模块用于 TX也用于读取CANerrorStatusOD_GET(H1001, ...)0x1001 Error registerCO_EM_process()计算后写入co-em_fifoEM FIFOproducer 发送队列和0x1003历史共用CO_GET_CNT(ARR_1003) 1UFIFO 数组大小实际可保存历史条数是ARR_1003OD_GET(H1014, ...)0x1014 COB-ID EMCY决定 producer CAN-ID 和 enabled 状态CO_GET_CO(TX_IDX_EM_PROD)TX buffer indexEMCY producer 使用的 CAN TX bufferOD_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接收其他节点默认范围 EMCYnodeId当前节点号默认 EMCY CAN-ID 0x80 nodeId4.1 为什么em_fifo是ARR_1003 1环形 FIFO 需要保留一个空槽来区分“空”和“满”fifoWrPtr fifoPpPtr - 空 fifoWrPtrNext fifoPpPtr - 满所以fifoSize 0x1003 历史容量 1 实际容量 fifoSize - 1CO_Emergency.c文件顶部也用fifoSize 7、实际容量6的图示说明了这个关系。5.CO_error()错误变化如何进入内部状态和 FIFOCO_errorReport()和CO_errorReset()都是宏最终进入CO_error()#defineCO_errorReport(em,errorBit,errorCode,infoCode)CO_error(em,true,errorBit,errorCode,infoCode)#defineCO_errorReset(em,errorBit,infoCode)CO_error(em,false,errorBit,CO_EMC_NO_ERROR,infoCode)5.1errorBit转成数组下标和位掩码uint8_tindexerrorBit3;uint8_tbitmask1U(errorBit0x7U);例如errorBit 0x12index 0x12 3 2 bitmask 1 2 0x04 目标位 errorStatusBits[2] 的 bit25.2 重复调用不会重复入队源码先判断状态是否变化if(setError){if(errorStatusBitMasked!0U){return;}}else{if(errorStatusBitMasked0U){return;}errorCodeCO_EMC_NO_ERROR;}效果如下调用当前 bit结果report1直接返回report0置位并写 FIFOreset0直接返回reset1清位并写 FIFOerror code 改成0x0000这与 EMCY “同一 error event 只发送一次”的协议原则一致。5.3CO_error()先留空 error registerCO_error()准备的errMsg是uint32_terrMsg((uint32_t)errorBit24)|CO_SWAP_16(errorCode);此时只放入byte0..1 errorCode byte2 暂空 byte3 errorBitbyte2 不在这里填是因为 error register 需要综合全部errorStatusBits[]和CO_CONFIG_ERR_CONDITION_xxx宏计算。这个工作放在周期性的CO_EM_process()更合适。6.CO_EM_process()计算0x1001并发送 EMCYCANopenNode 官方 Doxygen 说明CO_EM_process()必须周期调用它会检查部分通信错误、计算 OD0x1001并在必要时发送 EMCY。2源码顺序是检查 CAN driver 的错误状态变化。根据errorStatusBits[]计算errorRegister写入*em-errorRegister。若当前上下文不允许发送则返回。若 producer、FIFO、TX buffer、inhibit time 条件满足则发送一帧 EMCY。6.1 CAN driver 错误也会变成 EM 输入CO_EM_process()读取uint16_tCANerrStem-CANdevTx-CANerrorStatus;如果与CANerrorStatusOld不同就把变化转换成CO_error()调用。例如CAN driver 状态CANopenNode errorBiterrorCodeTX/RX warningCO_EM_CAN_BUS_WARNINGCO_EMC_NO_ERRORTX passiveCO_EM_CAN_TX_BUS_PASSIVECO_EMC_CAN_PASSIVETX bus offCO_EM_CAN_TX_BUS_OFFCO_EMC_BUS_OFF_RECOVEREDTX overflowCO_EM_CAN_TX_OVERFLOWCO_EMC_CAN_OVERRUNRX overflowCO_EM_CAN_RXB_OVERFLOWCO_EMC_CAN_OVERRUN6.20x1001是由条件宏汇总的默认配置中CANopenNode 预定义了三类条件#defineCO_CONFIG_ERR_CONDITION_GENERIC(em-errorStatusBits[5]!0U)#defineCO_CONFIG_ERR_CONDITION_COMMUNICATION((em-errorStatusBits[2]!0U)||(em-errorStatusBits[3]!0U))#defineCO_CONFIG_ERR_CONDITION_MANUFACTURER((em-errorStatusBits[8]!0U)||(em-errorStatusBits[9]!0U))这就是errorStatusBits[]与0x1001的关系前者是细粒度内部状态后者是 CANopen 标准对象中对错误类别的汇总。6.3 发送前补齐 byte2producer 分支中满足条件后源码补齐 byte2em-fifo[fifoPpPtr].msg|(uint32_t)errorRegister16;然后复制 8 字节并发送(void)memcpy((void*)em-CANtxBuff-data,(void*)em-fifo[fifoPpPtr].msg,sizeof(em-CANtxBuff-data));(void)CO_CANsend(em-CANdevTx,em-CANtxBuff);CO_EM_fifo_t的字段顺序是msg后接info所以这次 8 字节复制会把msg和info一起送入 CAN TX buffer。7. FIFO 与0x1003事件队列和历史读取共用一套存储errorStatusBits[]保存当前状态FIFO 保存状态变化事件。例如过压出现 - report - FIFO 加一条错误事件 过压仍然存在 - report - 状态未变不加 过压恢复 - reset - FIFO 加一条恢复事件 过压已经恢复 - reset - 状态未变不加OD_read_1003()中subindex0返回fifoCountsubindex1返回最新错误。它从fifoWrPtr往回数因此0x1003:01是最新一条0x1003:02是次新一条。写0x1003:00 0会清空错误历史计数if(CO_getUint8(buf)!0U){returnODR_INVALID_VALUE;}em-fifoCount0;它清的是历史读取计数不是直接清errorStatusBits[]当前状态。8.CO_CONFIG_EM配置宏影响哪些源码路径CANopenNode 官方配置文档列出CO_CONFIG_EM的可选 flagproducer、producer COB-ID configurable、producer inhibit、history、consumer、status bits、callback pre、timer next。3配置作用主要源码路径CO_CONFIG_EM_PRODUCER启用本节点 EMCY 发送CO_EM_init()初始化0x1014和 TX bufferCO_EM_process()发送CO_CONFIG_EM_PROD_CONFIGURABLE允许运行期配置 producer COB-IDOD_read_1014()/OD_write_1014()CO_CONFIG_EM_PROD_INHIBIT启用发送抑制时间OD_write_1015()、inhibitEmTimerCO_CONFIG_EM_HISTORY启用错误历史OD_read_1003()/OD_write_1003()CO_CONFIG_EM_CONSUMER接收其他节点 EMCYCO_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_us9.CO_CONFIG_EM_STATUS_BITS补充说明CO_CONFIG_EM_STATUS_BITS容易被忽略因为它不影响 EMCY 帧格式也不是 CiA 301 标准的固定对象号。CANopenNode 官方配置文档对它的定义是从 OD 访问 Error status bits。3它的作用可以概括为把CO_EM_t.errorStatusBits[]暴露给一个自定义 OD 项供 SDO/调试工具/厂商扩展读取或写入内部错误位图。9.1 初始化时需要OD_statusBits头文件的CO_EM_init()参数说明写明OD_statusBits是用于访问CO_EM_t中errorStatusBits的自定义 OD entry这个 entry 需要在 subindex0上提供(CO_CONFIG_EM_ERR_STATUS_BITS_COUNT / 8)字节变量并要求 IO extension。源码在CO_CONFIG_EM_STATUS_BITS打开时才会展开这个参数。对应初始化分支#if((CO_CONFIG_EM)CO_CONFIG_EM_STATUS_BITS)!0em-OD_statusBits_extension.objectem;em-OD_statusBits_extension.readOD_read_statusBits;em-OD_statusBits_extension.writeOD_write_statusBits;(void)OD_extension_init(OD_statusBits,em-OD_statusBits_extension);#endif9.2 读路径读取内部错误位图OD_read_statusBits()的核心行为是把内部状态位复制出去OD_size_t countReadLocalCO_CONFIG_EM_ERR_STATUS_BITS_COUNT/8U;...(void)memcpy((void*)(buf),(constvoid*)(em-errorStatusBits[0]),countReadLocal);因此如果你通过 SDO 读取这个自定义 OD 项读到的是当前errorStatusBits[]的原始位图。它比0x1001更细但也更偏 CANopenNode 内部实现。9.3 写路径可直接改内部错误位图OD_write_statusBits()的核心行为是反向复制OD_size_t countWriteCO_CONFIG_EM_ERR_STATUS_BITS_COUNT/8U;...(void)memcpy((void*)(em-errorStatusBits[0]),(constvoid*)(buf),countWrite);这说明它不是只读诊断入口而是可写入口。写入后下一次CO_EM_process()会基于新的errorStatusBits[]重新计算0x1001。但要注意直接写errorStatusBits[]不会像CO_errorReport()那样自动生成 FIFO 事件也不会自动形成一帧 EMCY。需要事件上报时应用仍应调用CO_errorReport()/CO_errorReset()。9.4 它和0x1001、0x1003的区别对象/入口内容是否标准固定对象是否触发 EMCY0x1001错误类别汇总是否只是当前状态输出0x1003最近错误历史 bytes0..3是否只是历史读取OD_statusBits内部errorStatusBits[]原始位图否由工程自定义否直接读写不产生事件CO_errorReport()错误发生事件输入API是入 FIFO 后由CO_EM_process()发送9.5 什么时候需要打开建议按用途决定场景建议只学习 EMCY producer/history 主流程可不打开避免混淆需要通过 SDO 观察内部每个CO_EM_xxxbit打开并在 OD 中放一个自定义 byte array需要调试CO_CONFIG_ERR_CONDITION_xxx为什么使0x1001置位打开有帮助希望主站通过 OD 强行清/置内部错误位可以打开但要明确这不会自动生成 EMCY 事件工程上更推荐应用错误的正常生命周期仍用CO_errorReport()/CO_errorReset()OD_statusBits主要作为调试和诊断入口。10. 从机工程中的最小使用路径应用层上报自定义错误时优先使用 manufacturer 范围的errorBit#defineAPP_EM_MOTOR_OVERLOAD(CO_EM_MANUFACTURER_START0U)voidapp_report_motor_overload(CO_t*co,uint32_tdetail){CO_errorReport(co-em,APP_EM_MOTOR_OVERLOAD,CO_EMC_DEVICE_SPECIFIC,detail);}voidapp_reset_motor_overload(CO_t*co,uint32_tdetail){CO_errorReset(co-em,APP_EM_MOTOR_OVERLOAD,detail);}调试时按这个顺序检查1. CAN 总线上是否出现 0x80 Node-ID 的 8 字节帧 2. SDO 读 0x1001看错误类别位是否变化 3. SDO 读 0x1003:00看历史条数 4. SDO 读 0x1003:01看最新错误 bytes 0..3 5. 确认 CO_EM_process() 被周期调用 6. 确认当前上下文允许发送 EMCY 7. 检查 0x1014 bit31 是否禁用了 producer 8. 检查 0x1015 是否导致发送被抑制 9. 若打开 STATUS_BITS读自定义 OD_statusBits 看内部 bit 是否符合预期最小移植检查表检查项预期CO_GET_CNT(EM) 1工程里确实有 Emergency 对象0x1001存在UNSIGNED8可被 EM 模块绑定0x1003启用 history 时存在容量和ARR_1003一致0x1014启用 producer 时存在默认值能得到0x80 nodeId0x1015启用 inhibit 时存在单位是100 usOD_statusBits仅启用CO_CONFIG_EM_STATUS_BITS时需要自定义 OD entryCO_EM_process()在 CANopen 主循环中周期调用TX buffer indexTX_IDX_EM_PROD不与其他对象冲突11. 参考资料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 官方 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 官方 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 ↩︎ ↩︎