2. CAN总线协议基础:CAN2.0A/B协议、CAN帧类型与帧结构详解
好,咱们进入正题。上一章聊了CAN总线能干什么,这一章咱们得把它的“语言”搞清楚——也就是CAN协议本身。说白了,协议就是一套规矩,规定数据怎么打包、怎么发送、怎么识别。你想想看,总线上那么多节点,要是没个规矩,那不乱套了?
2.1 CAN2.0A与CAN2.0B:标准帧和扩展帧的“前世今生”
CAN协议最早是德国Bosch公司搞出来的。早期版本叫CAN 2.0A,后来发现11位的ID不够用了,就升级成了CAN 2.0B。我个人习惯把2.0A叫“标准帧”,2.0B叫“扩展帧”。
它们最大的区别在哪?仲裁场的长度。
- CAN 2.0A(标准帧):标识符(ID)只有11位。这意味着总线上最多能定义2^11 = 2048个不同的消息ID。在早期的车上,够用了。
- CAN 2.0B(扩展帧):标识符(ID)扩展到29位。2^29 = 5亿多个ID,几乎用不完。现在的车,尤其是高端车,节点多、功能复杂,基本都用扩展帧。
关键点:CAN 2.0B的控制器是向下兼容CAN 2.0A的。也就是说,一个支持2.0B的节点,可以正常收发2.0A的标准帧。但反过来,一个只支持2.0A的老旧节点,收到扩展帧时可能会报错。我在项目里就遇到过这种兼容性问题,后来统一升级了控制器才解决。
咱们用个表格对比一下,一目了然:
| 特性 | CAN 2.0A(标准帧) | CAN 2.0B(扩展帧) |
|---|---|---|
| 标识符(ID)长度 | 11位 | 29位 |
| 最大ID数量 | 2048 | 5亿+ |
| 帧头开销 | 较小 | 较大(多出17位) |
| 兼容性 | 基础版本 | 向下兼容2.0A |
| 应用场景 | 简单系统、老车型 | 复杂系统、新车型 |
2.2 CAN帧的四种类型:数据、远程、错误、过载
CAN总线上跑的数据,都是以“帧”为单位的。就像你寄快递,得有个包裹。CAN帧一共有四种,每种干的事不一样。
2.2.1 数据帧(Data Frame)
这是最常用的帧,用来发送实际数据。比如发动机转速、车速、车门状态,都装在数据帧里。一个数据帧最多能带8个字节的数据。为什么是8个?这是早期设计时定的,考虑到实时性和总线负载,8个字节够用了。我做过一个项目,需要传输一个32位的浮点数,加上一个16位的状态字,刚好6个字节,一个帧搞定。
2.2.2 远程帧(Remote Frame)
这个有点意思。远程帧本身不带数据,它的作用是“请求”别的节点发数据。比如,一个节点想获取某个传感器的值,它就可以发一个远程帧,目标节点收到后,就会回复一个数据帧。
避坑指南:我曾经在调试一个多节点系统时,发现总线负载异常高。查了半天,发现是一个节点不停地发远程帧请求数据,而目标节点响应不过来,导致总线拥堵。后来我加了一个超时机制,才解决了这个问题。所以,远程帧要慎用,别滥用。
2.2.3 错误帧(Error Frame)
当节点检测到总线上的数据有问题时,就会主动发送错误帧,通知其他节点“刚才那包数据有问题,别用了”。错误帧由6个连续的显性位组成,这违反了CAN的位填充规则,所以其他节点也能检测到,并做出相应处理。
2.2.4 过载帧(Overload Frame)
这个帧用来表示“我忙不过来了,慢点发”。当一个节点接收数据的速度跟不上发送速度时,它会发送过载帧,让发送方等一等。过载帧的结构和错误帧有点像,但用途完全不同。
咱们总结一下这四种帧的用途:
- 数据帧:发送数据。
- 远程帧:请求数据。
- 错误帧:报告错误。
- 过载帧:请求延迟。
2.3 帧结构详解:一个数据帧的“五脏六腑”
咱们以最常用的数据帧为例,拆开看看里面到底有什么。一个标准数据帧,从开始到结束,包含以下几个部分:
- 帧起始(SOF):1个显性位。表示一帧的开始,同步所有节点的时钟。
- 仲裁场:包含11位ID(标准帧)和1位RTR位(远程帧请求位)。RTR位为0表示数据帧,为1表示远程帧。
- 控制场:包含6位。其中4位是DLC(数据长度码),表示数据场有多少个字节(0-8)。
- 数据场:0-8个字节。这就是你要发送的实际数据。
- CRC场:15位CRC校验码 + 1位CRC界定符。用来检查数据在传输过程中有没有出错。
- 应答场(ACK):2位。发送方发完数据后,会释放总线,接收方如果正确收到,就会在ACK槽位发送一个显性位,告诉发送方“我收到了”。
- 帧结束(EOF):7个隐性位。表示一帧的结束。
嗯,这里要注意,位填充是个隐藏的规则。CAN协议规定,如果连续出现5个相同的电平(全是0或全是1),就必须插入一个相反的电平。这是为了保证时钟同步。所以,你实际在总线上看到的波形,和理论上的帧结构,中间还隔了一层位填充。
下面这张图,是我用SVG画的,把标准数据帧的结构展示得清清楚楚:
2.4 扩展帧的结构差异
扩展帧(CAN 2.0B)和标准帧的结构基本一样,主要区别在仲裁场。扩展帧的仲裁场包含29位ID,分成了两部分:11位基本ID + 18位扩展ID。控制场里多了一个IDE位(标识符扩展位),用来区分是标准帧还是扩展帧。
IDE位为0,表示标准帧;IDE位为1,表示扩展帧。这个位在仲裁时也很关键,因为它会影响帧的优先级。标准帧的IDE位在控制场,而扩展帧的IDE位在仲裁场。所以,标准帧的优先级天然高于扩展帧。嗯,这是协议设计时故意这么做的,为了保证兼容性。
注意:在同一个网络中,标准帧和扩展帧可以共存。但你要小心,它们的ID空间是独立的。也就是说,标准帧的ID 0x100和扩展帧的ID 0x100,是两个完全不同的消息。我在一个项目中就踩过这个坑,两个节点用了相同的ID,但一个是标准帧,一个是扩展帧,结果通信一直对不上。
2.5 位填充:一个容易被忽略的细节
前面提到了位填充,这里再强调一下。CAN协议规定,在SOF到CRC场之间,如果连续出现5个相同的位,就必须插入一个相反的电平。比如,你连续发了5个显性位(0),那么在第5个位之后,必须插入一个隐性位(1)。
为什么要这么做?因为CAN总线没有单独的时钟线,所有节点靠检测电平的跳变来同步时钟。如果长时间没有跳变,节点的时钟就会漂移,导致采样错误。位填充保证了总线上有足够的跳变,让所有节点保持同步。
接收方在收到数据后,会自动去掉这些填充位,恢复原始数据。所以,你作为开发者,不需要手动处理位填充,但你要知道它的存在。尤其是在计算总线负载时,位填充会增加额外的开销。一个8字节的数据帧,理论上是108位(标准帧),但加上位填充后,实际可能达到120位左右。