4. LIN 2.2与2.1协议差异
调度表,说白了就是 LIN 总线的“交通指挥员”。
它决定了什么时候该发哪条报文,谁先谁后,间隔多久。我刚开始接触 LIN 2.x 时,总觉得调度表不就是个循环列表嘛,有啥好研究的?后来在项目里被坑过几次,才明白这东西的细节真不少。
4.1 调度表的结构
一个调度表,本质上是一个有序的帧时隙(Frame Slot)列表。每个时隙指定了:
- 帧 ID:要发送的报文标识符
- 帧类型:无条件帧、事件触发帧、偶发帧或诊断帧
- 时隙长度:该帧占用的时间(以 Tbit 为单位)
我习惯把调度表想象成一张“节目单”。主节点按节目单的顺序,一条一条往下执行。执行完最后一条,再跳回第一条,如此循环。
关键点:调度表是主节点独有的概念。从节点根本不关心调度表,它们只负责响应主节点发起的帧头。
来看一个典型的调度表定义(LDF 文件片段):
Schedule_tables {
Main_Schedule {
LIN_MSG_1 delay 10 ms;
LIN_MSG_2 delay 15 ms;
LIN_MSG_3 delay 10 ms;
LIN_MSG_4 delay 20 ms;
}
}
这个调度表包含 4 个条目。主节点会先发 LIN_MSG_1 的帧头,等 10ms 后发 LIN_MSG_2,再等 15ms……循环往复。
嗯,这里要注意:delay 是帧与帧之间的间隔,不是帧本身的持续时间。帧本身的长度由波特率和数据字节数决定,系统会自动计算。
4.2 触发方式
调度表的触发方式,我把它分成三类:
| 触发方式 | 说明 | 典型应用 |
|---|---|---|
| 连续循环 | 调度表从头到尾循环执行,永不停止 | 常规周期性报文(如车速、转速) |
| 单次执行 | 调度表执行一遍后自动停止 | 诊断请求、配置命令 |
| 条件触发 | 满足特定条件时才执行某个调度表 | 休眠唤醒、故障模式 |
你想想看,如果所有报文都用连续循环,那诊断报文就得等很久才能插进去。所以实际项目中,我们通常会准备多个调度表,按需切换。
我的习惯:至少准备 3 个调度表——正常运行表、诊断表、休眠表。这样切换起来干净利落。
4.3 动态切换实战
动态切换,就是运行时从一个调度表跳到另一个调度表。这是 LIN 2.x 相比 1.x 的一大进步。
我曾经在一个项目中,遇到过一个诡异的问题:切换调度表后,总线突然丢帧了。查了半天,发现是切换时机不对——在帧传输过程中切换,导致当前帧被截断。
避坑指南:切换调度表的最佳时机是当前帧传输完成之后、下一帧开始之前。我建议在帧完成中断里做切换,而不是在主循环里随意切换。
动态切换的典型流程:
- 主节点检测到某个条件(如按键按下、故障发生)
- 主节点等待当前帧传输完成
- 主节点更新调度表指针,指向新调度表
- 新调度表从第一条开始执行
代码实现上,我一般这样写:
// 调度表切换函数
void LIN_ScheduleSwitch(uint8_t newTable) {
// 等待当前帧完成
while (LIN_IsFrameInProgress()) {
// 空等待,或者喂狗
}
// 更新调度表索引
currentSchedule = newTable;
scheduleIndex = 0;
// 触发新调度表的第一帧
LIN_StartFrame(scheduleTable[currentSchedule][scheduleIndex]);
}
这里有个细节:切换后从第一条开始执行,而不是从上次中断的位置继续。这是 LIN 协议规范的要求,也是为了保证确定性。
4.4 多调度表协同
实际项目中,很少只用一张调度表。我参与的一个车身控制项目,用了 5 张调度表:
- Normal_Schedule:正常运行,周期 20ms
- Diagnostic_Schedule:诊断模式,周期 50ms,包含诊断帧
- Sleep_Schedule:休眠前,只发唤醒帧和休眠帧
- Bootloader_Schedule:刷写模式,周期 10ms,高优先级
- Error_Schedule:故障模式,只发故障码
切换逻辑用状态机管理,清晰明了。
经验之谈:调度表切换的响应时间,直接影响系统的实时性。我一般要求切换延迟不超过一个帧周期。如果切换逻辑太复杂,可以考虑用硬件定时器辅助。
4.5 调度表与总线负载
调度表的设计,直接决定了总线负载率。我见过一些新手,把所有报文都塞进一张调度表,结果总线负载飙到 80% 以上。
计算总线负载的公式很简单:
负载率 = (所有帧的总传输时间) / (调度表周期) × 100%
举个例子:
| 帧 ID | 数据长度 | 传输时间 (20kbps) |
|---|---|---|
| 0x01 | 4 字节 | 2.4 ms |
| 0x02 | 6 字节 | 3.2 ms |
| 0x03 | 8 字节 | 4.0 ms |
| 0x04 | 2 字节 | 1.6 ms |
如果调度表周期是 20ms,总传输时间 = 2.4 + 3.2 + 4.0 + 1.6 = 11.2ms,负载率 = 56%。
我个人建议,总线负载率控制在 50% 以下,留出余量给诊断和突发报文。超过 70% 就要小心了,很容易丢帧。
4.6 调度表设计原则
总结几条我踩坑踩出来的原则:
- 优先级高的帧放前面:比如安全相关的报文,放在调度表开头
- 周期短的帧多放几次:比如 10ms 的帧,可以在 50ms 的调度表里出现 5 次
- 诊断帧单独放一张表:避免影响正常通信
- 切换时保留上下文:切换前保存当前状态,切换后能恢复
小技巧:用 Excel 或脚本工具自动生成调度表。手动写 LDF 文件容易出错,尤其是帧多的时候。我写过一个 Python 脚本,输入帧列表和周期,自动输出调度表定义。
好了,调度表这部分就聊到这儿。记住一句话:调度表是 LIN 总线的灵魂,设计好了,系统跑得稳;设计不好,各种奇怪问题都来了。