LIN节点远程刷写:基于UDS诊断协议的OTA落地实践
发布时间:2026/9/16 23:14:11 锦皓数字建站

去年接到一个车门控制器项目客户提了个让我印象很深的需求软件改了一版修复偶发问题但固件始终躺在开发部门量产线和售后都等着用传统方式一台台刷。这让我下决心把“基于UDS诊断协议的LIN通信OTA升级”真正落地。LIN总线的速率不高节点MCU资源也紧张但用UDS诊断协议做远程刷写能够复用现有产线和售后诊断工具链这是它最大的价值。这篇文章我会从帧格式、传输层、服务裁剪到刷写状态机和实测踩坑完整梳理一遍适合正在做车身电子Bootloader、LIN从节点OTA的嵌入式工程师参考。1. 低速总线的觉醒LIN节点为什么也要远程刷写1.1 传统刷写方式的成本压力很多工程师觉得LIN是“低端总线”节点无非是车窗、门锁、座椅控制器软件稳定后基本不动。但实际项目中门模块这类节点恰恰是改版最频繁的。用户反馈车窗防夹误触发、触摸按钮偶尔失灵这类Bug修好后固化在Flash里的频率比我们想象中高得多。传统刷写方式无非两条路产线下线时用调试器或诊断仪逐台刷售后则要拆门板、拆控制器甚至返厂。每台车拆装一次门板的人工成本、时间成本比固件本身贵得多。要是遇到批量召回级别的软件缺陷传统刷写链路的成本几乎不可接受。OTA的核心诉求不是“炫技”而是把“物理接触”变成“总线通信”把固件从开发环境直接送进ECU。1.2 LIN节点的算力与存储边界LIN节点通常使用8位或16位MCU比如PIC18F系列、S12系列Flash从16KB到64KB不等RAM经常只有1KB到4KB主频也就几十兆赫。这和ESP32这类跑着成熟OTA框架的SoC完全不是一个物种。ESP32有现成的双bank切换、网络传输、加密回滚方案而LIN节点所有这些都要自己写连“把固件包完整接收下来再刷”这种想法都得先想想RAM够不够。所以LIN上的OTA方案必须接受一个现实固件无法整包接收只能边收边写Flash编程期间还不能让看门狗把系统复位了。这也是为什么很多项目选择在Bootloader中实现一套轻量UDS诊断协议配合LIN传输层做分包传输而不是移植一套完整的SOA/NVMe等重型框架。1.3 从时间维度算一笔升级账LIN常见波特率是19.2kbps每一帧标准诊断报文从Break、同步场、PID到数据场和校验场差不多要花12ms。如果一次升级要传44KB固件每帧连续帧只能带6字节用户数据理论上需要7500多帧光传输就要接近100秒。再加上调度表槽位间隙、响应等待、Flash擦写时间整包刷完耗时3到5分钟很正常。参数数值LIN波特率19.2 kbps一帧诊断报文耗时约12 ms单帧有效用户数据6字节固件大小44 KB所需连续帧数约7506帧理论传输时间约90秒实际完整升级耗时3~5分钟很多人在立项阶段会问“这么慢能用吗”实际上没问题。升级通常发生在停车状态或售后工位几分钟的等待完全可以接受关键是不能刷到一半失败导致控制器变砖。2. LIN诊断机制的底子帧结构、传输层和调度表2.1 诊断报文的物理层与帧结构LIN总线上的诊断通信有两条专用帧主请求帧的PID是0x3C从响应帧的PID是0x3D。也就是说无论哪个从节点诊断请求都挂在0x3C上所有诊断响应都挂在0x3D上具体是哪个节点的消息靠帧内容里的NAD节点地址区分。一帧LIN诊断报文在波形上包含Break场、同步场、PID、数据段和校验场。标准诊断帧通常带8字节数据段其中第一字节是NAD第二字节是PCI协议控制信息后面才是UDS服务相关的字节。很多刚接触LIN的同事会把它和CAN帧搞混实际上LIN传输层更接近串口上的TP层靠NAD和PCI组合完成寻址和拆包。2.2 传输层的SF/FF/CF拆包规则UDS一条服务消息通常只有几个字节比如0x10 02请求就是2字节一条诊断请求并发的数据量远远超过这一帧8字节的承载能力。所以LIN诊断协议规定了一套和CAN TP非常像的拆包规则单帧SF、首帧FF、连续帧CF。单帧SFPCI只占1字节高4位是0x0低4位表示用户数据长度最多6字节。适合0x10 02、0x27 01这类短请求。首帧FFPCI占2字节第一个字节高4位是0x1低4位和第二个字节一起表示整条诊断消息的总长度。FF自身能带6字节数据。连续帧CFPCI占1字节高4位是连续帧序号从1开始递增用来告诉接收方这是第几块数据。帧类型PCI格式有效数据长度SF单帧0x L0~6字节FF首帧0x1 LL6字节CF连续帧0xN 07字节注意连续帧CF的数据段中PCI只占1字节所以每帧能带7字节用户数据。很多人会奇怪前面我算升级时间为什么按6字节算因为刷写用的0x36传输数据报文按ISO 14229的格式要求数据段里有blockSequenceCounter和完整数据块实际映射到TP层正好是满6字节载荷这是块格式设计决定的。2.3 调度表就是LIN总线上的“红绿灯”LIN通信不同于CAN的仲裁机制它由主节点维护一个调度表Schedule Table总线上所有帧的发送时机都由主节点决定。诊断请求帧0x3C和从响应帧0x3D必须被安排在调度表里的特定槽位中。这个机制对OTA影响很大。正常情况下调度表里每个槽位10ms或20ms混着车窗状态、门锁反馈等周期报文。如果诊断报文只在某个周期槽位发送刷写效率会非常低。比较成熟的做法是在Bootloader或者编程会话中让主节点切换到一个“刷写专用调度表”诊断槽位占比大幅提高甚至连续发送0x3C请求把带宽尽量让给OTA。切换调度表这个动作本身要小心我在第五章会讲一个因为切换时机不对导致的超时案例。3. UDS服务在LIN平台上的裁剪与映射3.1 刷写链路不可省略的UDS服务UDS服务非常多但LIN上的OTA只需要其中一部分。我的做法是先把“刷写链路”的服务画出来逐个确认0x10 诊断会话控制从默认会话切到编程会话子功能0x02ECU只有进入编程会话才允许执行Flash擦写。0x27 安全访问配合seed/key机制防止非授权刷写。通常进入编程会话后必须解锁顺序不能反。0x31 例程控制用来做Flash擦除、编程完整性检查、内存检查。擦除操作很多时候比较慢可能触发0x78响应等待。0x34 请求下载告诉ECU“我要在哪个地址开始写、写多少字节”ECU检查地址范围后回复最大块长度。0x36 传输数据真正把固件内容分段送进ECU的核心服务带块序号和块数据。0x37 请求传输退出收尾确认ECU收到后可以返回“整个传输完成”的状态。0x11 ECU复位刷写完成后复位ECU跳到新APP运行。0x3E 待机握手在整个刷写期间需要周期性的待机握手防止ECU从编程会话跳出回默认会话。这些服务在大方向上和CAN上的OTA没本质区别关键在于LIN带宽低每个服务的响应格式要尽可能精简避免“服务套服务”。3.2 哪些UDS功能在LIN上可以直接砍做产品的人总想把所有诊断服务都塞进去我在LIN项目上的原则是除了刷写必需的服务其他都按需评估。0x19读取DTC和0x14清除DTC两个服务按理说跟刷写没有直接关系但我会在升级前检查里保留0x19用来确认节点有没有影响刷写的故障。如果DTC显示Flash编程电压异常或通讯丢失直接阻止OTA避免带上故障刷成半砖。0x2E按地址写数据和0x22按地址读数据在LIN上我倾向于砍掉或者仅保留RAM区映射因为LIN传输能力有限用0x2E做大块数据写入的效率很低而且地址越界保护做得不好容易把应用区写坏。0x28通信控制、0x85设置DTC等跟OTA流程没有强相关能省就省。砍服务不是越少越好而是要让刷写路径最短、错误分支最清晰。3.3 NRC是排查问题的第一把钥匙UDS服务面对异常情况时用负响应码NRC告诉主机哪里出了问题。LIN项目里我遇到最多的几个NRC整理出来供参考NRC含义常见触发场景0x11服务不支持Bootloader里没实现0x34或APP里没实现0x190x13报文长度或格式错误请求下载参数长度写错块序号缺失0x22条件不满足还没进入编程会话就先发0x340x24请求序列错误0x36还没发完直接来了0x370x31请求超出范围Flash擦除例程请求了一个非法区块0x33安全访问拒绝没有seed就发了key0x35密钥错误安全算法算出的key不对0x72编程失败Flash写入校验失败0x73块序号错误0x36的块序号跳号或重复0x78响应待定擦除Flash耗时较长ECU先回0x78完成后才回最终响应遇到NRC不要太早怀疑协议栈。多数情况下0x73是主机上位机没有按顺序递增blockSequenceCounter0x22则是刷写工具没有按流程先切会话。用CANoe或周立功的LIN工具抓一遍报文对照时序图定位问题通常很快。4. OTA刷写流程落地状态机、传输循环与回滚策略4.1 升级前检查与会话/安全切换上面讲了一堆标准服务落到实际工程里我习惯先把OTA流程定成一条明确的状态链默认会话 → 编程会话 → 安全解锁 → 前置检查 → 擦除 → 传输 → 校验 → 复位。在写代码之前先理清一个问题Bootloader和APP应该如何划分最稳妥的做法是Boot区只放“最小的刷写驱动UDS服务”APP区放业务功能。MCU上电后先跑Boot区Boot区判断APP区开头是否有合法的启动向量和校验标记合法则跳转到APP不合法则停留在Boot等待诊断刷写。状态机里每个状态都要有超时处理。我见过很多同事只做正常流程结果刷写中途上位机断连ECU卡在“传输数据”状态无法回到默认会话车型下线测试没法进行。所以每个状态都要有独立超时超时后自动回退到安全状态。4.2 0x34/0x36/0x37主传输循环的设计细节传输循环是整个OTA的核心。以一次典型刷写为例主机先发0x34请求下载报文中携带memoryAddress和memorySize。ECU收到后会检查地址是否落在允许刷写的Flash范围内。这个范围检查非常关键很多“刷写变砖”都是因为没做地址边界限制一条越界请求直接擦掉了Boot区。0x34成功后主机开始循环发0x36。每个0x36报文里包含blockSequenceCounter和块数据。ECU端收到一个块校验块序号是否正确、数据地址是否连续然后把数据写入Flash或暂存RAM缓冲区。Flash写入耗时较长所以我在实际项目里会让ECU在写Flash期间临时屏蔽其他非诊断任务同时保证LIN主节点那边不要连续狂发0x36而是等待ECU完成写入后再发下一帧。0x37请求传输退出是整个流程的收尾服务主机发0x37后ECU可以执行一次整体校验比如对写入区域做CRC或取反校验。校验通过才返回0x00并允许ECU复位否则回0x72负响应主机需要重新发起刷写。4.3 Flash分区策略与异常恢复方案Flash分区决定了OTA能不能在意外掉电后“活回来”。LIN节点MCU的Flash通常不大但再怎么紧张也建议至少分三块Bootloader区、APP区、参数/状态区。如果Flash容量允许进一步的方案是双Bank一个Bank运行当前APP另一个Bank存放新固件刷写完成后通过启动标志切换。这种方案最安全因为升级过程中旧APP一直可用一次失败不会影响当前功能。但很多8位MCU根本没有双Bank机制只能退而求其次先擦APP区再写入新固件。一旦中途掉电APP区可能是空的或半截的ECU只能停留在Boot区等待重新刷写。对于这种情况我在Boot区加了“强制编程模式”的逻辑上电后如果检测到APP无效就进入编程会话等待合法刷写。Boot区本身不做在线更新或者只在极严格的条件下才允许更新Boot区这是最后的保命手段。5. 实测中的三个“翻车现场”与排查记录5.1 从响应超时到调度表槽位冲突第一次跑通OTA时现象很诡异上位机发送0x10 02请求ECU有时能回正响应有时直接超时。抓波形后发现问题不在UDS层而在LIN调度表。上位机切到编程会话后我让主节点切换了刷写专用调度表。但新调度表里只增加了0x3C主请求帧的发送槽位没有给0x3D从响应帧留足等待窗口。从节点的响应发出来后要等下一轮0x3C的槽位触发调度主节点才会去接收0x3D。如果0x3C发得太快从节点还在处理内部逻辑响应就会错过接收窗口。解决办法是在刷写调度表里把0x3C和0x3D安排成一组“请求后紧接响应窗口”并留足至少10ms的响应时间对于擦除这类耗时较长的0x31服务主机必须支持0x78响应等待不能等着等着就报超时。5.2 LIN模式下串口发送数据也会触发接收中断项目用到PIC18F45K80的LIN模块调试时遇到一个特别容易误导人的问题代码里明明只往串口发送数据却总是触发接收中断。排查了很久才发现这是LIN模式下UART的硬件回环行为。某些MCU的USART模块在LIN模式下发送数据和接收数据共用同一根总线引脚硬件会把发送的数据又回送到接收路径中导致“自己发的数据被自己接收”。这在LIN半双工总线上是正常的物理现象但因为触发了接收中断MCU如果不做过滤会把“自己刚发出的数据”当成诊断请求来处理轻则干扰逻辑重则把状态机冲乱。解决办法是在发送数据期间关闭接收中断或者根据帧ID过滤只有收到Break之后的合法PID才进入诊断协议解析。5.3 诡异的NRC 0x72校验和算法之外的坑有一次刷写测试0x36传输了大约几百块后ECU返回NRC 0x72翻译过来是programmingFailure。大多数人第一反应是Flash写入校验和不对。我把抓包和Flash读取结果反复比对发现实际写进去的字节和报文里的字节完全一致算法也没错。最后定位到问题出在块序号上。我的上位机脚本在32位计数器边界处把blockSequenceCounter发重复了一次。ECU端认为序号跳变触发了编程保护返回0x72。这类问题在低速总线加长固件包时很容易出现排查建议是先确认是否是“块序号跳变/重复”导致的失败再回头查校验和算法。从那以后我的ECU端代码里增加了块序号连续性检测把序号错误单独返回NRC 0x73而不是笼统地归到0x72。这样主机端可以区分到底是“序号错了”还是“Flash写入失败”排查效率高很多。如果你也要在LIN上落地OTA我的建议是先别急着写传输循环把调度表、Flash分区、异常回退设计好再开始实现UDS服务。协议栈本身不复杂真正决定项目成败的往往是这些看起来不起眼的边缘路径。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。