第2章:CAN通信基础

各位同学,咱们今天聊聊CAN通信的基础。说实话,很多刚入行的工程师觉得CAN很简单,不就是两根线嘛。但我在项目里见过太多因为基础不牢导致的坑——信号干扰、丢帧、总线off,这些问题追根溯源,往往就是物理层或数据链路层没搞明白。

好,咱们开始。这一章我会把CAN总线的物理层、数据链路层、报文结构,还有DBC文件怎么解析,一次性给你讲透。

2.1 CAN总线物理层

CAN总线物理层,说白了就是信号怎么在线上跑。我习惯把它分成三块:电平标准、拓扑结构、终端电阻。

2.1.1 差分信号与电平

CAN用两根线——CAN_H和CAN_L,靠差分电压传数据。为什么用差分?抗干扰强啊。你想想看,如果外界有电磁干扰,两根线上受到的干扰基本一样,一减就抵消了。

具体电平是这样的:

状态 CAN_H电压 CAN_L电压 差分电压
显性(Dominant) 3.5V 1.5V 2.0V
隐性(Recessive) 2.5V 2.5V 0V

注意啊,显性电平会覆盖隐性电平。这就是CAN总线仲裁的基础——谁先发显性位,谁就赢。

⚠️ 避坑指南: 我曾经遇到一个项目,CAN通信时好时坏。查了两天,最后发现是CAN_H和CAN_L接反了。嗯,这种低级错误,但真容易犯。接线前一定用万用表量一下。

2.1.2 终端电阻

CAN总线两端必须各接一个120Ω的终端电阻。为什么?为了消除信号反射。我见过有人偷懒不接,结果高速通信时全是误码。

终端电阻的作用:

  • 匹配总线阻抗,防止信号反射
  • 保证隐性电平时的总线电压稳定
  • 显性时提供电流回路
💡 小技巧: 用万用表量CAN_H和CAN_L之间的电阻,如果总线没上电,应该测到60Ω左右(两个120Ω并联)。如果测到120Ω,说明只接了一个终端电阻;如果开路,一个都没接。

2.2 CAN数据链路层

数据链路层,说白了就是报文怎么组织、怎么发、怎么收。这部分我建议你重点理解帧格式和仲裁机制。

2.2.1 CAN帧类型

CAN总线有四种帧:

  • 数据帧:传数据用的,最常见
  • 远程帧:请求对方发数据
  • 错误帧:检测到错误时发
  • 过载帧:接收方忙不过来时发

咱们做UDS诊断,主要用数据帧。远程帧偶尔也会用到,比如请求某个节点的诊断数据。

2.2.2 仲裁机制

CAN总线是多主从结构,谁都能发。那冲突了怎么办?靠ID仲裁。

ID越小,优先级越高。显性位(0)覆盖隐性位(1)。所以ID为0x100的节点,优先级比ID为0x200的高。

我刚开始做CAN开发时,总觉得仲裁是硬件自动完成的,不用管。直到有一次,两个ECU同时发报文,低优先级的那个一直发不出去,导致功能超时。嗯,从那以后,我设计ID分配时都会留好优先级余量。

2.3 CAN报文结构

咱们重点看数据帧。标准帧和扩展帧的区别,就是ID长度不同。

2.3.1 标准帧(11位ID)

结构如下:

SOF | 11位ID | RTR | IDE | r0 | DLC | 数据段(0-8字节) | CRC | ACK | EOF
  • SOF:帧起始,1位显性
  • ID:11位标识符,决定优先级
  • RTR:远程帧标志,0是数据帧,1是远程帧
  • DLC:数据长度,0-8
  • 数据段:最多8字节
  • CRC:15位校验
  • ACK:应答位

2.3.2 扩展帧(29位ID)

结构类似,但ID变成29位:

SOF | 11位ID | SRR | IDE | 18位扩展ID | RTR | r1 | r0 | DLC | 数据段 | CRC | ACK | EOF

做UDS诊断时,我个人习惯用扩展帧。因为29位ID可以编码更多信息,比如源地址、目标地址、诊断类型等。当然,标准帧也能用,看项目需求。

🔑 核心要点: CAN报文一次最多传8字节数据。UDS诊断服务如果数据量大,需要分包发送。比如0x22服务读多个DID,一次可能读几十个字节,那就得分多个CAN帧传。

2.4 DBC文件解析

DBC文件,说白了就是CAN报文的"翻译官"。它告诉工具:哪个ID对应什么信号,信号在哪个字节的哪个位,怎么换算成物理值。

2.4.1 DBC文件结构

一个典型的DBC文件包含:

  • VERSION:版本信息
  • NS_:新符号段
  • BS_:波特率定义
  • BU_:节点定义
  • BO_:报文定义
  • SG_:信号定义
  • CM_:注释
  • VAL_:值描述

2.4.2 报文定义示例

看一个实际例子:

BO_ 100 ECU1_Status: 8 ECU1
 SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU2
 SG_ CoolantTemp : 16|8@1+ (1,-40) [-40|215] "degC" ECU2
 SG_ ErrorFlag : 24|1@1+ (1,0) [0|1] "" ECU2

我来逐行解释:

  • BO_ 100:报文ID是0x64(100十进制),名字叫ECU1_Status,长度8字节,发送节点是ECU1
  • SG_ EngineSpeed:信号名,起始位0,长度16位,Intel格式(@1+),缩放因子0.125,偏移0,范围0-8000,单位rpm,接收节点ECU2
  • SG_ CoolantTemp:起始位16,长度8位,缩放因子1,偏移-40,范围-40到215度
  • SG_ ErrorFlag:起始位24,长度1位,就是个布尔值
💡 实战经验: 我在项目中经常遇到DBC文件里信号定义和实际ECU代码不一致的情况。比如DBC里写的是Motorola格式(@1-),但代码里用的是Intel格式(@1+),结果解析出来的数据完全不对。所以拿到DBC文件后,第一件事就是拿一个已知的报文做交叉验证。

2.4.3 信号换算公式

物理值 = 原始值 × 缩放因子 + 偏移

举个例子:如果CAN报文里EngineSpeed的原始值是0x03E8(1000十进制),那物理值就是:

1000 × 0.125 + 0 = 125 rpm

反过来,如果要发125 rpm,原始值就是125 / 0.125 = 1000。

2.4.4 值描述

有些信号是枚举值,比如:

VAL_ 100 ErrorFlag 1 "Error" 0 "Normal" ;

这样在CANoe里看报文时,直接显示"Error"或"Normal",不用记0和1代表什么。

2.5 在CANoe中配置DBC

在CANoe里加载DBC文件很简单:

  1. 打开Simulation Setup
  2. 右键点击CAN总线,选择"Add DBC File"
  3. 选择你的.dbc文件
  4. 在Trace窗口里就能看到解析后的信号名和物理值了

我习惯在DBC里把信号名起得规范一些,比如用"ECU1_EngineSpeed"而不是"Speed"。这样在Trace里一眼就能看出是哪个节点的信号。

⚠️ 注意: DBC文件只定义了信号的编码方式,不包含诊断服务的定义。UDS诊断的DID、服务ID、子功能等,需要另外用CDD或ODX文件来定义。别把DBC和诊断数据库搞混了。

2.6 本章小结

好,咱们把CAN通信基础捋一遍:

  • 物理层:差分信号、终端电阻,这两样搞不定,通信就别想稳定
  • 数据链路层:帧格式、仲裁机制,理解这些才能看懂CANoe里的报文
  • 报文结构:标准帧和扩展帧,做UDS诊断建议用扩展帧
  • DBC文件:报文和信号的"翻译官",一定要会看会配

下一章,咱们正式进入UDS诊断协议。我会从诊断会话控制开始,一步步带你搭建完整的诊断通信流程。到时候,咱们会在CANoe里实际抓包、分析、模拟,那才是真正有意思的部分。

嗯,今天就到这儿。有问题随时问我。