第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总线仲裁的基础——谁先发显性位,谁就赢。
2.1.2 终端电阻
CAN总线两端必须各接一个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可以编码更多信息,比如源地址、目标地址、诊断类型等。当然,标准帧也能用,看项目需求。
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位,就是个布尔值
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文件很简单:
- 打开Simulation Setup
- 右键点击CAN总线,选择"Add DBC File"
- 选择你的.dbc文件
- 在Trace窗口里就能看到解析后的信号名和物理值了
我习惯在DBC里把信号名起得规范一些,比如用"ECU1_EngineSpeed"而不是"Speed"。这样在Trace里一眼就能看出是哪个节点的信号。
2.6 本章小结
好,咱们把CAN通信基础捋一遍:
- 物理层:差分信号、终端电阻,这两样搞不定,通信就别想稳定
- 数据链路层:帧格式、仲裁机制,理解这些才能看懂CANoe里的报文
- 报文结构:标准帧和扩展帧,做UDS诊断建议用扩展帧
- DBC文件:报文和信号的"翻译官",一定要会看会配
下一章,咱们正式进入UDS诊断协议。我会从诊断会话控制开始,一步步带你搭建完整的诊断通信流程。到时候,咱们会在CANoe里实际抓包、分析、模拟,那才是真正有意思的部分。
嗯,今天就到这儿。有问题随时问我。