4、CAN总线基础:CAN 2.0A/B帧结构、仲裁机制、位时序配置、采样点计算

好,咱们今天来啃一块硬骨头——CAN总线。说实话,做OBD模拟器,CAN总线是绕不过去的坎。你想想看,现在的汽车,从发动机到变速箱,从ABS到气囊,全靠这条总线在沟通。我当年第一次调CAN驱动时,被那个位时序折腾得够呛,后来才明白,这东西其实有规律可循。

4.1 CAN 2.0A/B帧结构:标准帧和扩展帧

CAN总线有两种帧格式,说白了就是身份证号的长短不同。

  • 标准帧(CAN 2.0A):11位标识符。够用吗?早期够用,现在嘛...有点挤。
  • 扩展帧(CAN 2.0B):29位标识符。多出来的18位,就是为了解决地址不够的问题。

我做过一个项目,客户非要兼容老协议,结果标准帧和扩展帧混着用,调试时差点没把我逼疯。嗯,这里要提醒你:同一个网络里,两种帧可以共存,但千万别指望它们能互相理解

来看一个典型的数据帧结构:

| SOF | 仲裁场 | 控制场 | 数据场 | CRC场 | ACK场 | EOF |
|  1  |  12/32 |   6    | 0-64   |  16   |   2   |  7  |

注意那个仲裁场,标准帧是12位(11位ID + RTR),扩展帧是32位(29位ID + SRR + IDE + RTR)。IDE位就是用来区分两者的——IDE=0是标准帧,IDE=1是扩展帧。

关键点:OBD模拟器里,我们通常用标准帧就够了。但如果你要模拟高端车型,扩展帧也得支持。我个人习惯是直接上扩展帧驱动,省得后面改。

4.2 仲裁机制:谁先说话谁有理

CAN总线的仲裁机制,说白了就是抢麦。多个节点同时发数据怎么办?看谁的ID小,谁就赢。

为什么会这样?因为CAN总线是“线与”逻辑——显性位(0)会覆盖隐性位(1)。所以ID越小,优先级越高。你想想看,刹车信号肯定比车窗信号紧急,对吧?

仲裁过程是这样的:

  1. 所有节点同时发送SOF位(显性)
  2. 接着发送仲裁场,逐位比较
  3. 谁先发出隐性位(1)但总线上是显性位(0),谁就退出
  4. 剩下的节点继续,直到只剩一个

我曾经遇到过一个问题:两个节点ID相同,结果总线一直冲突。查了半天才发现,是配置时ID写重复了。所以每个节点的ID必须唯一,这是铁律。

避坑指南:我曾经在OBD模拟器里把ID设成0x7E8(发动机响应ID),结果和真实ECU冲突了。后来我加了个配置项,让用户自己选ID范围。记住,模拟器别和真实节点抢地址。

4.3 位时序配置:把时间切成小段

CAN总线的位时序,就是把一个bit的时间切成若干段。为什么要切?因为要采样啊!你得在正确的时间点去读总线电平。

一个位时间通常分成四段:

段名缩写作用
同步段SS同步各节点时钟,固定1个Tq
传播段PTS补偿物理延迟,1-8个Tq
相位缓冲段1PBS1采样点前调整,1-8个Tq
相位缓冲段2PBS2采样点后调整,1-8个Tq

Tq是时间量子,由晶振分频得到。公式很简单:

Tq = 2 × (BRP + 1) / Fclk

其中BRP是波特率预分频器,Fclk是系统时钟频率。

举个例子,假设Fclk=16MHz,目标波特率500kbps:

位时间 = 1/500k = 2μs
如果BRP=0,Tq = 2/16M = 0.125μs
那么每个位需要 2/0.125 = 16个Tq

这16个Tq怎么分配?我一般这样配:

SS = 1 Tq
PTS = 7 Tq
PBS1 = 4 Tq
PBS2 = 4 Tq
总计 = 1+7+4+4 = 16 Tq

注意:不同MCU的CAN控制器,段名可能不一样。比如STM32叫BS1和BS2,其实就是PBS1和PBS2。别被名字搞晕了,看寄存器说明就行。

4.4 采样点计算:找准那个瞬间

采样点,就是CAN控制器读取总线电平的那个时刻。位置不对,数据就错了。

采样点位置计算公式:

采样点 = (SS + PTS + PBS1) / (SS + PTS + PBS1 + PBS2) × 100%

用上面的例子:

采样点 = (1 + 7 + 4) / 16 × 100% = 75%

75%是个好位置。为什么?因为总线信号有上升下降时间,采样点太靠前或太靠后,都可能采到不稳定电平。我个人习惯把采样点设在70%-80%之间。

来看一个实际配置代码(以STM32为例):

// 500kbps, 16MHz时钟
CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.CAN_SJW = CAN_SJW_1tq;      // 同步跳转宽度
CAN_InitStruct.CAN_BS1 = CAN_BS1_7tq;      // BS1 = 7 Tq
CAN_InitStruct.CAN_BS2 = CAN_BS2_4tq;      // BS2 = 4 Tq
CAN_InitStruct.CAN_Prescaler = 1;          // BRP = 1
// 采样点 = (1+7+4)/(1+7+4+4) = 75%

你可能会问:采样点能随便调吗?当然可以,但要注意:

  • 采样点太靠前(<60%):容易采到信号振铃
  • 采样点太靠后(>85%):可能来不及处理中断
  • 总线越长,传播段要越大

我的经验:调试OBD模拟器时,我习惯先用示波器看CAN总线波形,然后根据上升沿时间调整采样点。如果波形边沿很陡,采样点可以往后放;如果边沿缓,就得往前挪。说白了,采样点就是你和物理世界妥协的结果

4.5 实战:OBD模拟器的CAN配置

好了,理论说完了,咱们来点实际的。OBD模拟器通常用250kbps或500kbps。我建议用500kbps,因为现代车基本都支持。

配置步骤:

  1. 确定系统时钟频率
  2. 计算目标波特率对应的Tq数
  3. 分配SS、PTS、PBS1、PBS2
  4. 验证采样点是否在合理范围
  5. 写寄存器,初始化CAN控制器

最后送你一个口诀:同步固定1Tq,传播看线长,缓冲调采样,跳转抗干扰。记住了这个,CAN配置就不难了。

下一章咱们聊CAN报文收发和滤波,到时候你会看到,配置好位时序只是第一步,真正的坑还在后面等着呢。