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越小,优先级越高。你想想看,刹车信号肯定比车窗信号紧急,对吧?
仲裁过程是这样的:
- 所有节点同时发送SOF位(显性)
- 接着发送仲裁场,逐位比较
- 谁先发出隐性位(1)但总线上是显性位(0),谁就退出
- 剩下的节点继续,直到只剩一个
我曾经遇到过一个问题:两个节点ID相同,结果总线一直冲突。查了半天才发现,是配置时ID写重复了。所以每个节点的ID必须唯一,这是铁律。
避坑指南:我曾经在OBD模拟器里把ID设成0x7E8(发动机响应ID),结果和真实ECU冲突了。后来我加了个配置项,让用户自己选ID范围。记住,模拟器别和真实节点抢地址。
4.3 位时序配置:把时间切成小段
CAN总线的位时序,就是把一个bit的时间切成若干段。为什么要切?因为要采样啊!你得在正确的时间点去读总线电平。
一个位时间通常分成四段:
| 段名 | 缩写 | 作用 |
|---|---|---|
| 同步段 | SS | 同步各节点时钟,固定1个Tq |
| 传播段 | PTS | 补偿物理延迟,1-8个Tq |
| 相位缓冲段1 | PBS1 | 采样点前调整,1-8个Tq |
| 相位缓冲段2 | PBS2 | 采样点后调整,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,因为现代车基本都支持。
配置步骤:
- 确定系统时钟频率
- 计算目标波特率对应的Tq数
- 分配SS、PTS、PBS1、PBS2
- 验证采样点是否在合理范围
- 写寄存器,初始化CAN控制器
最后送你一个口诀:同步固定1Tq,传播看线长,缓冲调采样,跳转抗干扰。记住了这个,CAN配置就不难了。
下一章咱们聊CAN报文收发和滤波,到时候你会看到,配置好位时序只是第一步,真正的坑还在后面等着呢。