4、OBC CAN报文格式详解

好,咱们进入正题。这一章我打算把OBC的CAN报文格式掰开揉碎了讲清楚。说实话,很多刚入行的工程师,甚至一些做了两三年的同行,在报文格式上栽过跟头。我自己就见过因为ID定义搞混,导致整车网络瘫痪的案例——那场面,啧啧,真叫一个惨烈。

4.1 OBC CAN报文的ID定义(标准帧 vs 扩展帧)

先聊ID。CAN报文ID说白了就是报文的“身份证号”。在OBC这个节点上,ID决定了谁能发、谁能收、优先级多高。

标准帧(11位ID):这是最常用的格式。ID范围从0x000到0x7FF,总共2048个ID。OBC作为充电机,通常分配到的ID在0x1800xxxx这个范围附近——嗯,这里要注意,我说的是标准帧的ID范围,但实际项目中OBC的ID往往落在0x18xxxxxx这个扩展帧区域。为什么?因为整车网络节点多,标准帧的ID不够用。

扩展帧(29位ID):这才是OBC的主战场。29位ID,范围大得多。我参与过的几个项目,OBC的CAN ID基本都采用扩展帧。举个例子,充电状态报文,我们用的ID是0x18FF50E5。你想想看,29位ID里包含了优先级、目标地址、源地址等信息,解析起来比标准帧复杂,但灵活性高很多。

重要提醒:OBC的CAN通信协议中,标准帧和扩展帧不能混用。一个网络上,要么全用标准帧,要么全用扩展帧。我曾经见过一个项目,BMS发标准帧,OBC回扩展帧,结果CAN控制器直接报错——总线关闭了。那叫一个折腾。

我个人习惯,在OBC项目中统一使用扩展帧。原因很简单:扩展帧的29位ID可以承载更多信息,比如源地址、目标地址、报文类型等。标准帧11位,你只能硬编码,后期维护起来很痛苦。

4.2 OBC CAN报文的数据场结构(字节定义、位定义)

数据场,就是报文的“肉”。标准CAN数据场最多8个字节。OBC的报文,我一般按功能划分:

  • 字节0-1:状态标志位。比如充电状态、故障码、工作模式等。
  • 字节2-3:电压值。通常用两个字节表示,分辨率0.1V。
  • 字节4-5:电流值。同样两个字节,分辨率0.1A。
  • 字节6-7:温度值或预留位。

举个例子,OBC发送的充电状态报文,数据场结构如下:

字节0: 充电状态 (0x01=待机, 0x02=充电中, 0x03=充满, 0x04=故障)
字节1: 故障码 (0x00=无故障, 0x01=过压, 0x02=过流, 0x03=过温)
字节2-3: 输出电压 (单位0.1V, 例如0x0BB8 = 3000 = 300.0V)
字节4-5: 输出电流 (单位0.1A, 例如0x0064 = 100 = 10.0A)
字节6-7: 温度 (单位1℃, 例如0x0028 = 40℃)

位定义呢?说白了就是字节里的每个bit代表什么。比如字节0的bit0表示充电使能状态,bit1表示充电枪连接状态,bit2表示CC信号状态。我在项目中遇到过一个问题:某个OBC的故障码定义,bit3和bit4被两个不同团队用了不同的含义,结果联调时怎么都对不上。后来我强制要求:每个bit的定义必须在文档里写清楚,谁改谁签字。

我的经验:位定义最好用枚举类型,别用宏定义。枚举在调试时能看到具体含义,宏定义只能看到数字。你想想看,调试时看到0x03和看到FAULT_OVERCURRENT,哪个更直观?

4.3 OBC CAN报文的发送周期与触发方式

发送周期,就是报文多久发一次。OBC的报文,我一般分三类:

  • 周期性报文:比如充电状态报文,每100ms发一次。不管数据变没变,到点就发。
  • 事件触发报文:比如故障报文,只在故障发生时发一次。平时不发,省带宽。
  • 混合模式:平时周期性发送,事件发生时立即发送并缩短周期。比如正常时500ms发一次,故障时100ms发一次。

我个人建议OBC的充电状态报文用100ms周期。为什么?因为BMS需要实时监控充电状态,100ms的刷新率足够快,又不会占用太多总线带宽。我曾经见过一个项目,OBC把周期设成了10ms,结果CAN总线负载率直接飙到80%以上,其他节点都发不出报文了。

触发方式呢?除了定时器触发,还有硬件触发。比如充电枪插入时,OBC的CC信号会触发一个CAN报文发送。这种触发方式响应快,但要注意防抖——我踩过这个坑:充电枪插入瞬间,CC信号抖动,结果OBC连续发了十几个插入事件报文,把BMS搞懵了。

避坑指南:我曾经在某个项目中,OBC的故障报文用了事件触发,但没做去抖处理。结果一次过流故障,OBC在1ms内发了20多个故障报文,CAN总线直接过载。后来我加了个50ms的去抖定时器,问题解决。

4.4 OBC CAN报文的信号打包规则(Intel vs Motorola格式)

信号打包,说白了就是数据在字节里的排列方式。两种格式:Intel(小端)和Motorola(大端)。

Intel格式:低字节在前,高字节在后。比如一个16位的电压值0x0BB8(3000),在Intel格式下,字节0存0xB8,字节1存0x0B。这是最常用的格式,因为和CPU的存储方式一致,处理起来快。

Motorola格式:高字节在前,低字节在后。同样的0x0BB8,Motorola格式下,字节0存0x0B,字节1存0xB8。这种格式在德国车系中比较常见,比如奔驰、宝马。

我个人习惯,OBC项目统一用Intel格式。为什么?因为国内大部分MCU都是小端模式,用Intel格式可以直接memcpy,省去字节序转换的麻烦。但如果你和德国Tier1合作,他们可能要求用Motorola格式——这时候就得在代码里做转换了。

举个例子,一个16位的电压值,在两种格式下的打包方式:

// Intel格式 (小端)
uint16_t voltage = 3000; // 0x0BB8
uint8_t data[2];
data[0] = (uint8_t)(voltage & 0xFF);       // 0xB8
data[1] = (uint8_t)((voltage >> 8) & 0xFF); // 0x0B

// Motorola格式 (大端)
uint16_t voltage = 3000; // 0x0BB8
uint8_t data[2];
data[0] = (uint8_t)((voltage >> 8) & 0xFF); // 0x0B
data[1] = (uint8_t)(voltage & 0xFF);       // 0xB8

重要提醒:信号打包格式必须在协议文档里明确写清楚。我见过最坑的情况:OBC用Intel格式发电压值,BMS用Motorola格式解析,结果电压值变成了0xB80B(约2950V),BMS直接报过压故障,充电中断。后来查了三天才找到原因——就是打包格式不一致。

嗯,这一章的内容差不多就这些。报文格式是CAN通信的基础,搞不清楚后面所有工作都是白搭。下一章咱们聊聊OBC的充电流程,那才是真正有意思的部分。