数据链路层:LIN的帧结构与时序秘密
好,我们进入数据链路层。说白了,这一层定义了LIN总线上“一帧数据长什么样”。我当年刚接触LIN时,以为跟CAN差不多,结果被同步间隔字段坑了一把——示波器抓出来全是乱码。
帧结构是LIN通信的基石。每个节点都按这个规矩来收发,谁也不能例外。
LIN帧的整体结构
一帧完整的LIN报文,由主机任务和从机任务共同完成。主机发头,从机响应。帧结构分成两部分:
- 主机发送部分:同步间隔段、同步段、标识符段
- 从机响应部分:数据段、校验和段
先看这张图,把这几个段的位置和关系搞清楚。
关键点:帧头由主机任务发起,帧响应由被寻址的从机任务填充。主机只发头,从机只响应与自己ID匹配的帧。
同步间隔段(Break)
同步间隔段是帧开始的信号。它告诉所有节点:“准备一下,新帧来了。”
规范要求:同步间隔段至少持续13个显性位时间(低电平)。后续跟着至少1个位的总线空闲(隐性位,高电平),用来做间隔界定符。
为什么是13位?因为正常的字节帧最多10位(1起始+8数据+1停止),13位保证了任何正常字节都不会被误判为同步间隔。嗯,这是协议层的防误触发设计。
我曾经踩过的坑:有次调试一个门控模块,LIN总线偶尔漏帧。查了半天,发现主机发出的同步间隔只有11个位时间。有些从机芯片比较严格,11位不认。后来我把同步间隔设成14位,问题就消失了。
同步间隔的时序要求,记住这张表就够了:
| 参数 | 最小值 | 典型值 | 最大值 |
|---|---|---|---|
| 同步间隔长度 | 13位时间 | 13~14位时间 | 无严格上限(建议≤20位) |
| 间隔界定符 | 1位时间 | 2位时间 | 无 |
| 同步间隔低电平 | 显性 | 显性 | 显性 |
同步字段(Sync Byte)
同步字段的值固定为 0x55,也就是二进制 01010101。这可不是随便选的。
0x55 在总线上会产生均匀的上升沿和下降沿——每1位就跳变一次。从机利用这些边沿来测量主机的位时间,校准自己的时钟。说白了,就是从机跟着主机的节奏走。
LIN 2.x 的从机通过同步字段可以容忍最高 ±14% 的时钟偏差。我自己试过,用内部RC振荡器的不校准晶振,偏差在 ±5% 左右,完全没问题。
实用技巧:抓波形时,看到连续的5个方波脉冲(0101交替),那就是同步字段。如果同步字段波形畸变,多半是主从机波特率不匹配,或者总线上容性负载太重。
标识符字段(Protected Identifier)
标识符段是帧头的核心,共8位。它决定了这帧数据的“身份”和“去向”。
这8位的结构如下:
- ID[5:0]:6位的帧标识,取值范围 0~63
- P1, P0:2位的奇偶校验位
- P0 = ID0 ⊕ ID1 ⊕ ID2 ⊕ ID4
- P1 = ¬(ID1 ⊕ ID3 ⊕ ID4 ⊕ ID5)
你想想看,6位ID共64个可用标识符。LIN 2.x 定义了4种帧类型:无条件帧、事件触发帧、零星帧、诊断帧。通过ID范围来区分。
| 帧类型 | ID范围 | 用途 |
|---|---|---|
| 无条件帧 | 0x00 ~ 0x3B | 定期广播的信号,一个ID只能对应一个发送节点 |
| 事件触发帧 | 0x00 ~ 0x3B | 多个从机可以竞争发送,需搭配冲突解决 |
| 零星帧 | 0x00 ~ 0x3B | 按需发送,由主机根据事件触发 |
| 诊断帧 | 0x3C, 0x3D | 从机诊断、配置、编程 |
| 保留帧 | 0x3E, 0x3F | 未来扩展 |
我一般把ID设计成跟信号组一一对应。比如车门控制用ID 0x10-0x15,车窗用0x20-0x25。这样调试时一眼就能看出是哪个功能在通信。
避坑指南:注意ID的奇偶校验位是必须的。有些初学者直接写0x15作为ID,但LIN从机芯片需要的是经过校验的Protected ID。如果校验位错了,从机会直接忽略这个帧头。
数据段与校验和段
数据段最多8个字节,这也是LIN协议跟CAN相似的地方。但LIN的数据段长度受LEN字段约束,可以是1~8字节。
校验和段分两种模式:
- 经典校验和:只覆盖数据段(LIN 1.x兼容)
- 增强校验和:覆盖数据段 + 标识符段(LIN 2.x默认)
我个人习惯统一用增强校验和。除非项目明确要求兼容LIN 1.x从机。校验和算法很简单:所有字节相加,取反码,丢弃进位。
// 增强校验和计算示例
uint8_t calc_lin_checksum(uint8_t pid, uint8_t *data, uint8_t len) {
uint16_t sum = 0;
uint8_t i;
// 包含标识符段(Protected ID)
sum = pid;
// 累加数据段
for (i = 0; i < len; i++) {
sum += data[i];
}
// 进位回卷
while (sum > 0xFF) {
sum = (sum & 0xFF) + (sum >> 8);
}
// 取反码
return (uint8_t)(~sum & 0xFF);
}
嗯,这段代码我在三个项目里直接用。简单可靠,没有坑。
字节场与位时序
每个字节场使用标准的串行UART格式:1个起始位 + 8个数据位 + 1个停止位。LSB先发,MSB后发。这跟RS232一样,所以很多MCU直接用UART外设就能收发LIN。
但是——LIN的位时间不是固定的。它取决于波特率。常用波特率有:
- 1 kbps(低速,用于车身控制)
- 2.4 kbps(低速)
- 9.6 kbps(中速,最常用)
- 19.2 kbps(高速)
整个帧的传输时间,你按这个公式估算:
帧时间 ≈ 同步间隔(14位) + 间隔界定符(1位)
+ 同步字节(10位) + 标识符字节(10位)
+ N×数据字节×10位 + 校验和字节(10位)
+ 帧内响应空间(图中未标,约几位的间隔)
以1字节数据帧@19.2kbps为例:
总位数 ≈ 14+1+10+10+10+10 = 55位
时间 ≈ 55 / 19200 ≈ 2.86 ms
经验之谈:设计调度表时,给每个帧留足时间余量。我一般会在理论时间上再多留20%~30%。因为你测出来的实际波形有上升沿斜率、从机响应延迟,这些都会吃掉时间。
好了,帧结构、同步间隔、同步字段、标识符这几个概念就讲到这里。它们串联起来就是LIN数据链路层的完整骨架。调试时只要盯着这几个段看,大部分问题都能定位。不会?那就拿示波器抓一帧,对着这篇文章的结构对照分析,很快就能上手。