云计算百科
云计算领域专业知识百科平台

CANopen 在 BMS 系统中的 C 语言实战指南

摘要:本文面向电池管理系统(BMS)固件开发工程师,系统讲解 CANopen 协议在 BMS 场景下的工程落地方法。内容涵盖核心概念解析、开发环境搭建、对象字典配置、NMT 状态机管理、PDO 实时数据上报、SDO 参数读写、心跳监测与故障诊断,并给出典型 C 语言代码实例与常见报错排查步骤,帮助读者从零构建高可靠 BMS 通信节点。

关键词:BMS;CANopen;对象字典;NMT 状态机;PDO;SDO;心跳报文;嵌入式开发

目录

  • ① BMS 场景下的 CANopen 核心概念解析
  • ② 嵌入式开发环境搭建与协议栈集成
  • ③ 对象字典配置与 BMS 参数映射方法
  • ④ NMT 状态机管理与节点初始化流程
  • ⑤ PDO 通信机制实现电池数据实时上报
  • ⑥ SDO 服务调用完成参数读写操作
  • ⑦ 心跳报文监测与节点故障诊断逻辑
  • ⑧ 典型 C 语言代码实例与运行验证
  • ⑨ 常见通信报错分析与排查步骤
  • ⑩ 提升 BMS 通信稳定性的实用技巧

在电池管理系统(BMS)的开发过程中,通信协议的稳定性直接决定了整个储能系统或电动汽车的安全性与可靠性。很多嵌入式工程师在从理论转向实战时,往往会卡在协议栈的集成细节上:明明原理图连接无误,但上位机就是读不到电池电压,或者节点频繁掉线复位。这种“通而不畅”的现象,通常不是硬件故障,而是对 CANopen 协议在 BMS 特定场景下的应用逻辑理解不够透彻。CANopen 作为一种成熟的应用层协议,其优势在于标准化的对象字典和灵活的状态机管理,但若配置不当,极易导致实时性不足或数据丢包。

对于负责 BMS 固件开发的工程师而言,掌握 CANopen 不仅仅是学会调用几个 API 函数,更需要深入理解 NMT 状态机的流转机制、PDO 的映射规则以及 SDO 服务的交互时序。特别是在多节串联电池组的管理中,如何确保成千上万个参数能够有序、实时地传输,同时又能通过心跳机制快速定位故障节点,是衡量代码质量的关键指标。本文将剥离复杂的理论堆砌,直接切入工程实践,从环境搭建到代码落地,一步步还原一个高可靠 BMS 通信节点的构建过程。无论你是正在选型协议栈的新手,还是试图优化现有通信架构的资深开发者,接下来的内容都将提供可立即复用的实操方案,帮助你避开那些在调试阶段容易让人头疼的“坑”。

① BMS 场景下的 CANopen 核心概念解析

在 BMS 应用中,CANopen 并非简单的数据搬运工,而是一套严谨的协作规范。我们需要首先厘清几个在电池管理中至关重要的核心概念。首先是对象字典(Object Dictionary),它是 CANopen 节点的“数据库”,所有电池电压、温度、SOC(剩余电量)、SOH(健康状态)等参数都必须定义在这里。每个参数都有唯一的索引(Index)和子索引(Sub-index),例如,0x2000 可能代表单体电压数组,而 0x1008 则是厂商名称。理解对象字典的结构,是实现参数标准化访问的基础。

其次是通信模型。CANopen 提供了多种通信方式,但在 BMS 中,最常用的是生产者/消费者模型,主要通过 PDO(过程数据对象)实现。想象一下,BMS 作为生产者,周期性地将最新的电池数据“广播”出去,而 VCU(整车控制器)或充电桩作为消费者,只需监听即可获取数据,无需反复请求。这种机制极大地降低了总线负载,保证了数据的实时性。相比之下,SDO(服务数据对象)则更像是一种“点对点”的读写工具,适用于非实时的参数配置,如修改保护阈值或读取固件版本,它的传输速度较慢,但可靠性极高,适合偶尔发生的配置操作。

最后不能忽视的是网络管理(NMT)。BMS 系统上电后,节点不会立刻开始发送数据,而是必须经过 NMT 状态机的严格管控,从初始化状态过渡到预操作、操作状态等。只有进入“运行状态”,PDO 通信才会被激活。这一机制确保了所有节点在完成自检和参数同步前,不会向总线发送无效或错误的数据,从而避免了系统启动阶段的混乱。

为了帮助读者快速理解 PDO 与 SDO 的适用场景,下面从传输模式、实时性、可靠性、典型应用场景和数据长度限制等维度进行对比:

对比维度
PDO(过程数据对象)
SDO(服务数据对象)
传输模式 生产者/消费者模型,单向广播,无需应答 客户端/服务器模型,点对点,带确认应答
实时性 高,支持周期性或事件触发,适合高频数据 低,逐帧确认,适合低频配置操作
可靠性 无确认机制,靠 CRC 与总线仲裁保证 高,带确认与中止码(Abort Code),可校验
典型应用场景 电池电压、电流、温度等实时数据上报 修改保护阈值、读取固件版本、产线校准
数据长度限制 经典 CAN 单帧最多 8 字节,需映射压缩 支持分段传输(快速/普通),可传大块数据

简单来说,PDO 负责“快”,把实时数据以最小开销广播出去;SDO 负责“准”,用确认机制保证配置和诊断数据万无一失。在 BMS 工程中,两者各司其职、互为补充,合理搭配才能兼顾实时性与可靠性。

为了更直观地理解这些概念在一条真实总线上的协作关系,下面给出一个典型的 BMS CANopen 报文拓扑示意。BMS 节点作为生产者周期广播 PDO 与心跳,VCU/充电桩作为消费者监听;配置与诊断则通过 SDO 点对点完成:

#mermaid-svg-BA02cjg2cyT3Haa7{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BA02cjg2cyT3Haa7 .error-icon{fill:#552222;}#mermaid-svg-BA02cjg2cyT3Haa7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BA02cjg2cyT3Haa7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BA02cjg2cyT3Haa7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BA02cjg2cyT3Haa7 .marker.cross{stroke:#333333;}#mermaid-svg-BA02cjg2cyT3Haa7 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BA02cjg2cyT3Haa7 p{margin:0;}#mermaid-svg-BA02cjg2cyT3Haa7 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-BA02cjg2cyT3Haa7 .cluster-label text{fill:#333;}#mermaid-svg-BA02cjg2cyT3Haa7 .cluster-label span{color:#333;}#mermaid-svg-BA02cjg2cyT3Haa7 .cluster-label span p{background-color:transparent;}#mermaid-svg-BA02cjg2cyT3Haa7 .label text,#mermaid-svg-BA02cjg2cyT3Haa7 span{fill:#333;color:#333;}#mermaid-svg-BA02cjg2cyT3Haa7 .node rect,#mermaid-svg-BA02cjg2cyT3Haa7 .node circle,#mermaid-svg-BA02cjg2cyT3Haa7 .node ellipse,#mermaid-svg-BA02cjg2cyT3Haa7 .node polygon,#mermaid-svg-BA02cjg2cyT3Haa7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BA02cjg2cyT3Haa7 .rough-node .label text,#mermaid-svg-BA02cjg2cyT3Haa7 .node .label text,#mermaid-svg-BA02cjg2cyT3Haa7 .image-shape .label,#mermaid-svg-BA02cjg2cyT3Haa7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-BA02cjg2cyT3Haa7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BA02cjg2cyT3Haa7 .rough-node .label,#mermaid-svg-BA02cjg2cyT3Haa7 .node .label,#mermaid-svg-BA02cjg2cyT3Haa7 .image-shape .label,#mermaid-svg-BA02cjg2cyT3Haa7 .icon-shape .label{text-align:center;}#mermaid-svg-BA02cjg2cyT3Haa7 .node.clickable{cursor:pointer;}#mermaid-svg-BA02cjg2cyT3Haa7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BA02cjg2cyT3Haa7 .arrowheadPath{fill:#333333;}#mermaid-svg-BA02cjg2cyT3Haa7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BA02cjg2cyT3Haa7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BA02cjg2cyT3Haa7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BA02cjg2cyT3Haa7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BA02cjg2cyT3Haa7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BA02cjg2cyT3Haa7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BA02cjg2cyT3Haa7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BA02cjg2cyT3Haa7 .cluster text{fill:#333;}#mermaid-svg-BA02cjg2cyT3Haa7 .cluster span{color:#333;}#mermaid-svg-BA02cjg2cyT3Haa7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BA02cjg2cyT3Haa7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BA02cjg2cyT3Haa7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-BA02cjg2cyT3Haa7 .icon-shape,#mermaid-svg-BA02cjg2cyT3Haa7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BA02cjg2cyT3Haa7 .icon-shape p,#mermaid-svg-BA02cjg2cyT3Haa7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BA02cjg2cyT3Haa7 .icon-shape .label rect,#mermaid-svg-BA02cjg2cyT3Haa7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BA02cjg2cyT3Haa7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BA02cjg2cyT3Haa7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BA02cjg2cyT3Haa7 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

主控节点(消费者)

BMS 节点(生产者)

0x180+NodeID

0x700+NodeID

0x600+NodeID 读写请求

0x580+NodeID 应答

对象字典电压/温度/SOC

PDO 周期广播

心跳报文

SDO 服务器

VCU / 充电桩

SDO 客户端

通过这张图可以看到:PDO 与心跳走广播通道,保证实时性与存活监测;SDO 走点对点通道,保证配置与诊断的可靠性。两者在 COB-ID 上天然隔离,互不干扰,这正是 CANopen 在 BMS 中能够兼顾“快”与“准”的底层原因。

补充:CANopen 与 CANbus 的对比分析

很多初学者容易把 CANopen 和 CANbus 混为一谈,其实它们是两个不同层次的概念。CANbus(CAN 总线)是物理层与数据链路层的通信标准,定义了电气特性、帧格式、仲裁机制和错误处理,它只负责把数据帧可靠地从一个节点搬到另一个节点,至于帧里的字节代表什么含义,CANbus 本身并不关心。而 CANopen 是构建在 CANbus 之上的应用层协议,它利用 CAN 的帧结构,进一步规定了对象字典、通信对象(PDO/SDO/NMT/心跳)和状态机,让总线上的数据有了明确的语义。

为了帮助读者快速厘清两者的边界,下面从协议层次、数据组织、实时性、开发复杂度、适用场景等维度进行对比:

对比维度
CANbus(CAN 总线)
CANopen(应用层协议)
协议层次 物理层 + 数据链路层(ISO 11898) 应用层协议,构建于 CANbus 之上
数据语义 只传输原始帧,字节含义由用户自行约定 通过对象字典标准化数据含义,索引/子索引访问
通信方式 仅提供帧收发,无主从概念,所有节点平等竞争总线 定义 PDO/SDO/NMT/心跳等通信对象,有明确主从与生产者/消费者模型
实时性 高,硬件仲裁保证确定性 高,PDO 周期性/事件触发,继承 CAN 的确定性
开发复杂度 低,直接操作帧 ID 与数据 较高,需配置对象字典、映射参数、状态机
可维护性 差,数据格式依赖文档约定,易出错 好,对象字典统一管理,跨厂商可互操作
典型应用场景 简单点对点通信、自定义私有协议 多节点 BMS、伺服驱动、工业设备互联等标准化场景

简单来说,CANbus 是“路”,提供了一条可靠、实时的数据传输通道;CANopen 是“交通规则”,规定了路上跑的车(报文)如何编队、如何让行、如何报告故障。在 BMS 工程中,我们通常直接基于 CANbus 硬件,在其上运行 CANopen 协议栈,从而获得标准化、可维护、可扩展的通信架构。如果只是少量节点、数据格式固定,直接用 CANbus 裸帧也能满足需求;但当节点增多、参数繁杂、需要跨团队协作时,CANopen 的标准化优势就会充分体现出来。

② 嵌入式开发环境搭建与协议栈集成

工欲善其事,必先利其器。在开始编写 BMS 通信逻辑前,搭建一个高效的开发环境至关重要。目前主流的嵌入式平台如 STM32、NXP S32K 系列等,都拥有完善的 CAN 控制器支持。开发环境通常选择 Keil MDK、IAR EWARM 或基于 VS Code 的 GCC 工具链。关键在于协议栈的选择与集成。

对于资源受限的 MCU,可以选择轻量级的开源协议栈,如 CanFestival 或 OpenCANopen,但需注意其对 BMS 特定功能的裁剪与适配。若项目对功能安全(ISO 26262)有要求,则建议采用经过认证的商业协议栈。集成步骤通常如下:首先,将协议栈的核心源码(包括 CO_OD.c, CO_NMT.c, CO_PDO.c 等)复制到工程的驱动目录;其次,配置硬件抽象层(HAL),将协议栈的底层发送与接收接口映射到 MCU 的 CAN 驱动函数上。

这里需要特别注意中断优先级的配置。CAN 接收中断应设置为较高优先级,以确保报文不被丢失,但协议栈内部的任务调度(如心跳发送、PDO 周期处理)通常需要在主循环或定时器中断中执行。建议在 main.c 中建立一个时间基准定时器(如 1ms 中断),并在其中调用协议栈的 CO_TickFunction(),这是维持协议栈内部时钟和状态机流转的心脏。如果这一步遗漏,整个通信网络将陷入停滞。

为了让新手少走弯路,下面给出一个最小可用的集成清单,按顺序核对即可避免大部分“通而不畅”的问题:

  • 确认 CAN 外设时钟已使能:在 System_Init() 中开启 CAN 控制器所在总线的时钟(如 STM32 的 APB1),否则寄存器读写无效。
  • 配置 CAN 引脚复用:将 TX/RX 引脚映射到 CAN 外设,并设置为推挽复用模式,波特率按全网统一值(如 500kbps)配置。
  • 使能 CAN 接收中断:在 NVIC 中开启 CAN RX 中断,并设置合理优先级(建议高于普通外设中断)。
  • 注册协议栈时基:启动 1ms 定时器中断,在中断服务函数中调用 CO_TickFunction()。
  • 初始化协议栈:调用 CO_Init() 完成对象字典、NMT、PDO、SDO 等模块的初始化,并注册底层收发回调。
  • 下面是一个典型的协议栈集成骨架,展示了 HAL 层与协议栈的对接方式:

    /* ———- 硬件抽象层:CAN 收发接口 ———- */
    static void CAN_Send_Frame(uint32_t cob_id, uint8_t *data, uint8_t len) {


    /* 将 COB-ID 与数据打包成 CAN 帧,调用 MCU 驱动发送 */
    CAN_TxFrame(cob_id, data, len);
    }

    /* ———- 协议栈底层回调注册 ———- */
    void CO_Init(void) {


    /* 注册发送回调:协议栈内部所有报文都走这里 */
    CO_Register_SendCallback(CAN_Send_Frame);

    /* 注册接收回调:CAN 中断收到帧后交给协议栈解析 */
    CO_Register_RxCallback(CO_Process_RxFrame);

    /* 初始化对象字典、NMT、PDO、SDO 等模块 */
    CO_OD_Init();
    CO_NMT_Init();
    CO_PDO_Init();
    CO_SDO_Init();
    }

    /* ———- 1ms 定时器中断:协议栈时基 ———- */
    void Timer_ISR_1ms(void) {


    CO_TickFunction(); /* 驱动心跳、PDO 周期、超时等内部逻辑 */
    }

    /* ———- CAN 接收中断 ———- */
    void CAN_RX_ISR(void) {


    uint32_t cob_id;
    uint8_t data[8];
    uint8_t len;
    if (CAN_ReadFrame(&cob_id, data, &len)) {


    CO_Process_RxFrame(cob_id, data, len); /* 交给协议栈解析 */
    }
    }

    集成完成后,建议先用一个空节点(只发心跳、不映射 PDO)做冒烟测试:上电后观察心跳报文是否按 100ms 周期稳定发出。这一步能快速验证 HAL 层、时基和中断链路是否打通,再逐步叠加 PDO 与 SDO 功能,避免一次性集成过多模块导致问题难以定位。

    ③ 对象字典配置与 BMS 参数映射方法

    对象字典是 BMS 与外部世界交互的接口,其配置的合理性直接影响系统的可维护性。在 C 语言实现中,对象字典通常表现为一个结构体数组。针对 BMS 特性,我们需要规划好存储区域。

    首先,建议将 BMS 的参数按功能域划分,便于后续维护与扩展。一个典型的划分方式如下:

    功能域
    索引范围
    典型参数
    设备信息 0x1000-0x1FFF 厂商名称、设备类型、软件版本
    实时数据 0x2000-0x2FFF 单体电压、温度、总电流、SOC
    配置参数 0x3000-0x3FFF 过压阈值、欠压阈值、温度保护上限
    诊断信息 0x4000-0x4FFF 故障码、错误寄存器、运行日志

    例如,我们可以定义如下结构来映射电池数据:

    /* 定义 BMS 实时数据结构 */
    typedef struct {


    uint16_t cell_voltages[16]; // 16 串电池电压,单位 mV
    int16_t temperatures[8]; // 8 个温度点,单位 0.1°C
    uint16_t pack_current; // 总电流,单位 0.1A
    uint16_t soc; // 剩余电量,0-100%
    } BMS_RealTimeData_t;

    /* 全局实例 */
    BMS_RealTimeData_t bms_data;

    /* 定义 BMS 配置参数结构 */
    typedef struct {


    uint16_t ovp_threshold; // 过压保护阈值,单位 mV
    uint16_t uvp_threshold; // 欠压保护阈值,单位 mV
    uint16_t temp_limit; // 温度保护上限,单位 0.1°C
    } BMS_Config_t;

    /* 全局实例 */
    BMS_Config_t bms_config;

    在对象字典文件中,需要将上述变量与 CANopen 索引绑定。假设我们将实时数据映射到制造商自定义区域(0x2000-0x5FFF):

    /* 对象字典条目示例 */
    const CO_OD_entry_t CO_OD[CO_OD_NUM_ENTRIES] = {


    /* 索引 0x2001: 单体电压数组 */
    {

    0x2001, 0x00, 0x42, 0x00, (void*)&bms_data.cell_voltages, sizeof(bms_data.cell_voltages)},

    /* 索引 0x2002: 温度数组 */
    {

    0x2002, 0x00, 0x42, 0x00, (void*)&bms_data.temperatures, sizeof(bms_data.temperatures)},

    /* 索引 0x2003: SOC 值,只读 */
    {

    0x2003, 0x00, 0x07, 0x00, (void*)&bms_data.soc, sizeof(uint16_t)},

    /* 索引 0x3001: 过压保护阈值,可读写 */
    {

    0x3001, 0x00, 0x42, 0x00, (void*)&bms_config.ovp_threshold, sizeof(uint16_t)},

    /* 索引 0x3002: 欠压保护阈值,可读写 */
    {

    0x3002, 0x00, 0x42, 0x00, (void*)&bms_config.uvp_threshold, sizeof(uint16_t)}
    };

    这里的属性位(如 0x42 表示可读可写,0x07 表示只读)需要根据实际业务逻辑严格设定。对于电池电压等实时变化的量,通常设为只读,防止外部误写;而对于过压保护阈值等参数,则应设为可写,以便动态调整。映射时还要注意数据对齐和大小端问题,确保上位机解析出的数值与 MCU 内存中一致。

    最后,建议为每个对象字典条目补充注释文档,记录参数的单位、取值范围和物理含义。这样当团队协作或后续维护时,即使不看代码,也能通过对象字典快速定位每个参数的作用,避免“改一个字节、查半天文档”的尴尬。

    ④ NMT 状态机管理与节点初始化流程

    NMT(Network Management)状态机是 CANopen 节点的指挥中心。一个标准的 BMS 节点上电后,默认处于Initialization状态,完成硬件自检后自动进入Pre-Operational状态。在此状态下,节点可以响应 SDO 请求,进行参数配置,但不能发送 PDO 数据。只有当主控节点(Master)发送\”Start Remote Node\”命令后,BMS 才会进入Operational状态,正式开始周期性上报数据。

    为了帮助读者快速理解 NMT 状态机的流转关系,下面给出一个典型的状态迁移示意:

    #mermaid-svg-Ua3DUCTLoUbZ6smC{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ua3DUCTLoUbZ6smC .error-icon{fill:#552222;}#mermaid-svg-Ua3DUCTLoUbZ6smC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ua3DUCTLoUbZ6smC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ua3DUCTLoUbZ6smC .marker.cross{stroke:#333333;}#mermaid-svg-Ua3DUCTLoUbZ6smC svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ua3DUCTLoUbZ6smC p{margin:0;}#mermaid-svg-Ua3DUCTLoUbZ6smC .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ua3DUCTLoUbZ6smC .cluster-label text{fill:#333;}#mermaid-svg-Ua3DUCTLoUbZ6smC .cluster-label span{color:#333;}#mermaid-svg-Ua3DUCTLoUbZ6smC .cluster-label span p{background-color:transparent;}#mermaid-svg-Ua3DUCTLoUbZ6smC .label text,#mermaid-svg-Ua3DUCTLoUbZ6smC span{fill:#333;color:#333;}#mermaid-svg-Ua3DUCTLoUbZ6smC .node rect,#mermaid-svg-Ua3DUCTLoUbZ6smC .node circle,#mermaid-svg-Ua3DUCTLoUbZ6smC .node ellipse,#mermaid-svg-Ua3DUCTLoUbZ6smC .node polygon,#mermaid-svg-Ua3DUCTLoUbZ6smC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ua3DUCTLoUbZ6smC .rough-node .label text,#mermaid-svg-Ua3DUCTLoUbZ6smC .node .label text,#mermaid-svg-Ua3DUCTLoUbZ6smC .image-shape .label,#mermaid-svg-Ua3DUCTLoUbZ6smC .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ua3DUCTLoUbZ6smC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ua3DUCTLoUbZ6smC .rough-node .label,#mermaid-svg-Ua3DUCTLoUbZ6smC .node .label,#mermaid-svg-Ua3DUCTLoUbZ6smC .image-shape .label,#mermaid-svg-Ua3DUCTLoUbZ6smC .icon-shape .label{text-align:center;}#mermaid-svg-Ua3DUCTLoUbZ6smC .node.clickable{cursor:pointer;}#mermaid-svg-Ua3DUCTLoUbZ6smC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ua3DUCTLoUbZ6smC .arrowheadPath{fill:#333333;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ua3DUCTLoUbZ6smC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ua3DUCTLoUbZ6smC .edgeLabel{background-c

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » CANopen 在 BMS 系统中的 C 语言实战指南
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!