2. CAN协议基础:CAN 2.0A/B标准,帧格式与仲裁机制

大家好,我是你们的老朋友。今天咱们来聊聊CAN协议的基础。说实话,CAN协议在BMS里太常见了,几乎每个电池包都在用。我当年刚入行时,被CAN的各种帧格式搞得晕头转向,后来踩了不少坑才真正搞明白。今天我把这些经验分享给你,希望能帮你少走弯路。

2.1 CAN 2.0A与2.0B标准

先说说这两个标准。CAN 2.0A是早期版本,它的标识符只有11位。而CAN 2.0B扩展到了29位。你可能会问,为什么要扩展?

原因很简单——车上的节点越来越多。11位标识符最多支持2048个不同ID,在早期够用。但现在一辆车可能有几十个ECU,加上BMS内部的各种模块,11位就不够用了。我个人习惯在BMS项目中直接用CAN 2.0B,哪怕暂时用不到29位,也留个余地。

这里有个关键点:CAN 2.0B兼容CAN 2.0A。也就是说,2.0B的控制器可以接收2.0A的报文。但反过来不行。我在项目中遇到过,有人把2.0A的节点挂在2.0B的网络上,结果通信时好时坏。排查了半天才发现是兼容性问题。

避坑指南:我曾经在一个项目中,为了省成本用了只支持CAN 2.0A的控制器。结果后期要增加诊断功能时,发现ID不够用了。最后只能换控制器,损失不小。建议你从一开始就选支持CAN 2.0B的芯片。

2.2 帧格式详解

CAN协议有四种帧类型:数据帧、远程帧、错误帧、过载帧。咱们一个一个说。

2.2.1 数据帧

数据帧是最常用的。它负责把数据从一个节点传到另一个节点。结构上分为七段:

  • 帧起始(SOF):1个显性位,告诉总线“我要开始发了”
  • 仲裁段:包含标识符和RTR位。标准帧11位ID,扩展帧29位ID
  • 控制段:包含IDE位、保留位和DLC(数据长度码)
  • 数据段:0-8字节的数据。BMS里通常用来传电压、温度、电流
  • CRC段:15位CRC校验+1位CRC界定符
  • ACK段:2位。发送方发隐性位,接收方拉成显性表示确认
  • 帧结束(EOF):7个隐性位

嗯,这里要注意数据段最多8个字节。为什么是8个?因为CAN总线设计时考虑到实时性,8字节刚好够传一条完整的控制指令。我见过有人非要把10个字节塞进一帧里,结果只能拆成两帧发,反而增加了复杂度。

2.2.2 远程帧

远程帧是用来请求数据的。它没有数据段,但DLC字段会指明请求的数据长度。举个例子:BMS的主控想知道某个从模块的电压,就可以发一个远程帧,从模块收到后回复数据帧。

说实话,远程帧在实际BMS项目中用得不多。我更喜欢用周期性发送的方式,简单可靠。远程帧有个坑:如果多个节点同时请求同一个ID,可能会引发总线冲突。我曾经因为这个原因,把远程帧改成了定时发送,问题就解决了。

2.2.3 错误帧

错误帧是CAN协议自带的错误处理机制。当节点检测到总线错误时,会主动发送错误帧。结构很简单:6个显性位的错误标志 + 8个隐性位的错误界定符。

错误帧分两种:主动错误帧和被动错误帧。主动错误帧的6个显性位会覆盖总线上的正常数据,相当于“强行打断”。被动错误帧的6个位是隐性的,不会影响其他节点。

经验之谈:在BMS调试阶段,我经常用CAN分析仪抓错误帧。如果看到连续的错误帧,基本可以断定是物理层问题——比如终端电阻没接好、线缆过长、或者共模干扰太大。

2.2.4 过载帧

过载帧用于节点忙不过来时请求延迟。比如某个从模块正在处理数据,来不及接收新报文,就发过载帧让发送方等一等。

过载帧的结构和错误帧很像,也是6个显性位+8个隐性位。但触发条件不同:过载帧是节点主动发的,错误帧是检测到错误后自动发的。

我在BMS项目中很少遇到过载帧。因为BMS的通信节奏相对固定,只要设计合理,一般不会出现处理不过来的情况。但如果你用CAN做OTA升级,过载帧就可能频繁出现——大数据量传输时,接收缓冲区很容易满。

2.3 位填充机制

位填充是CAN协议里一个巧妙的设计。规则很简单:连续发送5个相同位后,自动插入一个相反位

为什么要这么做?因为CAN总线靠电平跳变来同步时钟。如果连续发太多相同的位,节点之间的时钟就会漂移。位填充保证了总线上每5个位至少有一次跳变。

举个例子:你要发数据0x00(二进制00000000),CAN控制器会自动在每5个0后面插入一个1。实际发出去的是000001000001... 接收方收到后,再把插入的1去掉,还原原始数据。

嗯,这里要注意:位填充只适用于数据帧和远程帧的SOF到CRC段。ACK段、EOF段和帧间隔不参与填充。我刚开始做CAN驱动时,自己写了个位填充算法,结果在边界条件上出了bug,导致某些特定数据发不出去。后来直接用硬件自带的CAN控制器,再也没出过问题。

关键点:位填充会影响实际传输时间。比如你要发8字节数据,理论上需要108位(SOF+仲裁+控制+数据+CRC+ACK+EOF)。但加上位填充后,实际可能变成120位左右。设计实时性时要考虑这个余量。

2.4 仲裁机制

仲裁是CAN协议最精彩的部分。它解决了“多个节点同时发送时谁先发”的问题。

原理很简单:显性位(0)优先于隐性位(1)。当两个节点同时发送时,它们逐位比较ID。谁的ID先出现显性位,谁就赢得仲裁,继续发送。输的节点自动转为接收模式,等总线空闲后再重发。

举个例子:节点A的ID是0x100(二进制000100000000),节点B的ID是0x200(二进制001000000000)。同时发送时,第3位(从高位算起)A是0,B是1。A的显性位覆盖了B的隐性位,A赢得仲裁。

你想想看,这个机制有多巧妙——仲裁过程不浪费任何带宽。输的节点自动退出发送,赢的节点继续发完整帧。不像其他总线协议,需要专门的仲裁帧或令牌传递。

在BMS中,我通常把关键报文(比如电池状态、故障告警)分配较低的ID值,确保它们能优先发送。非关键报文(比如日志信息)分配较高的ID值。这样即使总线繁忙,关键信息也不会被阻塞。

避坑指南:我曾经在一个项目中,把两个节点的ID设成了相同的值。结果它们同时发送时,仲裁无法区分,数据一直冲突。后来查了手册才发现,CAN协议要求每个节点必须有唯一的ID。这个坑踩得值,让我记住了这个细节。

2.5 小结

好了,今天的内容就到这里。咱们回顾一下:

  • CAN 2.0A用11位ID,2.0B用29位ID。建议直接上2.0B
  • 四种帧格式:数据帧传数据,远程帧请求数据,错误帧报告错误,过载帧请求延迟
  • 位填充保证时钟同步,每5个相同位插一个相反位
  • 仲裁靠显性位优先,ID越小优先级越高

这些是CAN协议的基础,也是BMS通信的基石。下一章咱们聊聊CAN在BMS中的具体应用——怎么设计报文格式、怎么分配ID、怎么处理错误。到时候我会分享更多实战中的经验。

有什么问题,欢迎在评论区留言。咱们下期见!