3. LIN总线协议层(一):报文帧结构
各位同学,今天我们正式进入LIN总线的核心——协议层。说白了,就是报文在总线上到底长什么样,怎么发,怎么收。
我记得刚接触LIN时,总觉得它比CAN简单太多,结果第一次调试就栽在同步间隔场的时长上。嗯,细节决定成败,咱们一步步来。
3.1 报文帧的完整结构
一个完整的LIN报文帧,由主机任务发送的帧头,和从机任务发送的响应组成。帧头固定由主机发出,响应可以由主机或从机发出。
整个帧的结构如下:
| 字段 | 长度 | 发送方 | 说明 |
|---|---|---|---|
| 同步间隔场 | 至少13位显性电平 | 主机 | 帧起始标志,唤醒总线 |
| 同步间隔分隔符 | 至少1位隐性电平 | 主机 | 间隔场与同步场的分界 |
| 同步场 | 1字节 (0x55) | 主机 | 用于从机时钟同步 |
| 标识符场 | 1字节 (PID) | 主机 | 包含ID和奇偶校验位 |
| 数据场 | 1~8字节 | 从机/主机 | 实际数据负载 |
| 校验和场 | 1字节 | 从机/主机 | 数据校验 |
关键点:整个帧结构中,只有同步间隔场是显性电平持续,其他所有场都是标准的UART字节格式(1起始位+8数据位+1停止位)。
3.2 各字段详解
3.2.1 同步间隔场
这是LIN帧最特殊的地方。它不是一个字节,而是一段至少13位(bit)的连续显性电平(逻辑0)。
为什么要这么长?因为LIN总线是单线,没有独立的时钟线。从机需要靠这个超长的显性电平来识别帧的开始,并重新同步自己的时钟。
避坑指南:我曾经在一个项目中,把同步间隔场设成了12位。结果大部分从机能正常工作,但有一款从机偶尔会漏帧。查了两天才发现,那款芯片要求至少13位。所以,保守起见,我建议你设成14~15位,留点余量。
同步间隔场之后,紧跟着至少1位的隐性电平(逻辑1),叫间隔分隔符。这个分隔符不能省,否则从机可能把间隔场和后面的同步场连在一起解析。
3.2.2 同步场
同步场固定为字节 0x55,二进制是 01010101。你想想看,这个字节在UART传输时,起始位+数据位+停止位,会形成一串均匀的方波:
起始位 0 + 数据位 10101010 + 停止位 1
→ 电平变化:0→1→0→1→0→1→0→1→0→1
从机就靠这个均匀的跳变沿来测量主机的位时间,然后调整自己的波特率。说白了,就是一次自动校准。
小技巧:调试时,用示波器抓同步场的波形,看跳变沿是否均匀。如果边沿间距不一致,说明主机的时钟抖动太大,或者总线上有干扰。
3.2.3 标识符场
标识符场也叫PID(Protected Identifier),包含6位的帧ID和2位的奇偶校验位。
帧ID范围是0x00~0x3F,共64个。其中:
- 0x00~0x3B:用于信号传输
- 0x3C:用于诊断帧(主机请求)
- 0x3D:用于诊断帧(从机响应)
- 0x3E~0x3F:保留,未来扩展用
奇偶校验位的计算方式如下:
P0 = ID0 ⊕ ID1 ⊕ ID2 ⊕ ID4
P1 = !(ID1 ⊕ ID3 ⊕ ID4 ⊕ ID5)
其中ID0是ID的最低位,ID5是最高位。P0是偶校验,P1是奇校验再取反。
为什么要有奇偶校验?我遇到过总线被干扰,ID从0x15变成了0x35,结果从机A和从机B同时响应,总线直接乱掉。有了奇偶校验,从机可以拒绝无效的ID,避免这种冲突。
3.2.4 数据场
数据场长度由LDF(LIN描述文件)定义,可以是1~8字节。注意,同一个ID在不同帧中数据长度必须固定,不能变。
数据字节的传输顺序是:先发低字节,再发高字节。每个字节内部,先发最低位(LSB first)。
举个例子,如果数据场要发送 0x1234:
字节0 = 0x34 (先发)
字节1 = 0x12 (后发)
3.2.5 校验和场
校验和场分两种:经典校验和(Classic Checksum)和增强校验和(Enhanced Checksum)。
| 类型 | 计算范围 | 适用版本 |
|---|---|---|
| 经典校验和 | 仅数据场 | LIN 1.x |
| 增强校验和 | 标识符场 + 数据场 | LIN 2.x |
计算方法是:将所有参与计算的字节相加,每次相加后如果结果超过0xFF,就减去0xFF(即取和模256,但进位加到最低位)。最后取反。
我个人的习惯:新项目一律用增强校验和。虽然多算一个PID,但安全性高很多。而且LIN 2.x的从机都支持。
3.3 报文帧类型
LIN总线定义了四种帧类型。我刚开始学的时候觉得有点乱,后来发现它们解决的是不同场景下的通信效率问题。
3.3.1 无条件帧
这是最基础的帧类型。主机发帧头,指定ID,对应的从机必须无条件地发送或接收数据。
特点:
- 一个ID只能分配给一个从机发送
- 每个调度周期都会发送
- 用于周期性信号,如车速、转速
3.3.2 事件触发帧
这个设计很巧妙。当多个从机共享同一个ID时,平时只有主机发帧头,从机不响应。只有当某个从机的数据发生变化时,它才响应。
如果多个从机同时响应,就会发生冲突。这时主机需要切换到无条件帧,逐个查询。
实际案例:我在做车门模块时,四个车窗的位置信号用了事件触发帧。平时总线很安静,只有某个车窗动了才发数据。总线负载率从40%降到了5%。
3.3.3 偶发帧
偶发帧和无条件帧很像,但它的发送时机是“有空才发”。如果总线空闲,且数据有更新,就发;否则跳过。
它通常用于非关键信号,比如环境温度、电池电压。这些信号变化慢,没必要每个周期都发。
3.3.4 诊断帧
诊断帧使用固定的ID:
- 0x3C:主机请求帧(从机接收)
- 0x3D:从机响应帧(从机发送)
诊断帧的数据场固定为8字节,遵循ISO 14229(UDS)协议。用于读取故障码、写入配置、执行例程等。
注意:诊断帧的优先级高于所有其他帧。在调度表中,诊断帧通常放在最前面。我曾经见过一个项目,诊断帧被普通帧堵住,导致ECU刷写超时。后来把诊断帧的时隙提前,问题就解决了。
3.4 知识结构图
下面这张图总结了本章的核心内容,帮你建立整体认知:
3.5 小结
这一章我们拆解了LIN报文帧的每个字段,也梳理了四种帧类型的使用场景。你可能会觉得内容有点多,但别急,这些都是基础中的基础。
我个人建议你先把帧结构记熟,特别是同步间隔场的时长和PID的校验算法。这两个地方是调试时最容易出问题的。