首页/新闻资讯/正文详情

UDS诊断协议核心机制:会话状态、安全访问与负响应排查

发布时间:2026/11/3 14:04:57 来源:云帆数科 栏目:资讯中心
UDS诊断协议核心机制:会话状态、安全访问与负响应排查
简介《汽车UDS诊断协议学习笔记PDF版》面向汽车电子初学者、诊断测试工程师及需要系统梳理ISO 14229的读者帮助建立UDS服务清单与请求响应逻辑。全包仅含1个PDF文件约10.49MB便于在电脑或平板上直接查阅。内容按诊断和通信管理、数据传输、已存储数据传输、输入输出控制、例程控制、上传下载六大功能单元展开覆盖0x10会话控制、0x11 ECU重置、0x27安全访问、0x19读取DTC、0x22/0x2E读写数据、0x31例程控制及0x340x37上传下载等常用服务并说明诊断仪与ECU之间“问—答”模式、正负响应和NRC的基本含义。已有1932人学习适合作为服务速查、协议入门和项目联调前的知识铺垫。1. 从一条 7F 说起UDS 难的不是码表是隐式状态把 UDSUnified Diagnostic Services当成一张 ISO 14229 的服务码表来背大概是理解这套协议最常见的翻车起点。从 0x10 到 0x87二十多个 SID 谁都能抄下来可一旦真车上的请求发出去回了一条7F 22 7F码表救不了你——因为 UDS 的每一次问答改变的不是数据本身而是 ECU 内部的一份隐式状态当前处在哪个会话、安全等级解到第几级、DTC 记录开关是开还是关、下载块计数器走到第几块。这些状态诊断仪看不见却直接决定下一条请求会不会被受理。所以真正值得搞懂的问题不是“0x22 是读数据”而是“为什么同一条 0x22在默认会话下被拒、在扩展会话下能过”。这份笔记把二十多个服务按功能单元拆开逐条讲价值也在这儿它给的不只是服务清单而是一张能反推 ECU 内部状态的地图。2. 会话与状态机0x10 为什么是所有诊断命令的基石2.1 DefaultSession 的门槛到底设在哪儿0x10 的请求只有两个字节10 xxxx 是 diagnosticSessionType取值范围 00 到 FF。协议自身定义了一批通用值——默认会话、编程会话、扩展会话、安全系统会话——剩下的码位留给主机厂自定义所以不同项目里10 03未必都是扩展会话这一点在跨项目复用脚本时非常容易踩空。关键在于默认会话是 ECU 跟着整车一起运行的常态此时它允许你读但不允许你写、不允许你刷、不允许你接管执行器。像 0x27 安全访问、0x2E 写 DID、0x31 例程控制、0x34 到 0x37 的下载链通常都要求处在非默认会话下。这不是协议在为难人而是一种失效安全的设计只要诊断仪拔掉或者超时ECU 会自动退回默认会话那些高危服务就自动关门了。会话在这里承担的其实是“授权时效”的角色比单纯的模式切换含义更重。2.2 正响应 0x50 与负响应 7F 的编码规则UDS 的响应编码有一条贯穿全篇的规则正响应的第一个字节等于请求 SID 加 0x40所以 0x10 的正响应是 0x50。紧随其后的是子功能回显你发10 03它就回50 03第三个字段 sessionParameterRecord 是服务器上报的会话相关参数最常见的实现思路是把它拆成 P2 和 P2* 两个时序参数告诉诊断仪“我这条响应最多多久回来、收到 0x78 之后最多再等多久”。负响应的格式则是固定的三字节7F 请求 SID NRC。第一个字节恒为 0x7F第二个字节回显你刚才请求的是哪个服务第三个字节才是真正的错误原因。这种设计的巧妙之处在于诊断仪不需要理解任何服务的内部细节只看第一个字节就能判断成败第二个字节定位到具体服务第三个字节查表即可。代价是 NRC 的表达能力有限同一个 0x22 可能因为七八种完全不同的原因失败后面排查那一章会专门拆这个。# 0x10 请求构造与响应解析把编码规则固化成函数 def build_session_request(session_type: int, suppress_pos_rsp: bool False) - bytes: # bit7 是抑制正响应位置 1 后 ECU 成功时不回 0x50失败仍然回 7F sub session_type | (0x80 if suppress_pos_rsp else 0x00) return bytes([0x10, sub]) def parse_session_response(data: bytes) - dict: if data[0] 0x7F: # 负响应7F 请求SID NRC return {ok: False, req_sid: data[1], nrc: data[2]} assert data[0] 0x10 0x40, 正响应 SID 必须等于请求 SID 0x40 return { ok: True, session: data[1] 0x7F, # 子功能回显去掉抑制位 p2_ms: int.from_bytes(data[2:4], big), # 常见实现P2 server max p2star_10ms: int.from_bytes(data[4:6], big), # 常见实现P2* 以 10ms 为单位 }这段代码里最容易被忽略的是抑制位。10 83和10 03在语义上是同一个请求只差“成功时要不要回话”。在总线负载紧张的刷写流程里抑制正响应能省下大量带宽但它同时意味着你失去了“请求已送达”的确认信号只能靠后续请求的响应来间接判断。我一般不建议在会话切换这种关键节点上用抑制位省下的那几毫秒不值得。2.3 0x11 的副作用重置把会话打回原点0x11 ECUReset 看起来很简单实际上是个典型的“有副作用”服务。请求格式是11 xxxx 是 resetType常见的有硬复位、钥匙开关复位、软复位以及一个特殊值 0x04——快速下电使能。只有请求 0x04 时正响应里才会多出一个 powerDownTime 参数取值范围 00 到 FE 有效FF 表示时间不可用或出错。协议在这里有一条硬性约束服务器在执行重置之前必须先把肯定响应发出去。因为一旦真的复位通信链路就断了响应根本没机会发出。这条约束对自动化测试的影响非常大——脚本里收到 0x51 的正响应不代表 ECU 已经重启完成只代表它答应要重启了。更要命的是后半句重置成功后服务器应激活 DefaultSession。这意味着你前面辛苦建立起来的扩展会话、解掉的安全等级在 0x11 之后全部清零。血泪经验是很多刷写脚本在例程里插了一次复位后面所有依赖扩展会话的请求集体报 7F日志上看像是“突然不支持了”实际只是会话被打回原点。复位之后必须重新走一遍 0x10 切会话、0x27 解锁的完整流程没有捷径。2.4 0x3E 与 0x28、0x85维持状态的三类服务有一类服务不产生任何业务数据但它们决定了别的服务能不能工作。0x3E TesterPresent 是最典型的一个ECU 内部有一个 S3 计时器常见默认值是 5 秒只要这段时间内没收到任何诊断请求它就自动退回默认会话。诊断仪靠周期性发3E 00来续命工程上常写成3E 80即打开抑制正响应位让 ECU 别回话省带宽。0x28 CommunicationControl 控制的是非诊断报文的收发刷写前把应用报文关掉能显著降低总线负载、避免刷写过程中被无关报文打断。0x85 ControlDTCSetting 控制的是故障码记录开关刷写和某些标定例程执行期间必须关掉否则 ECU 会把刷写过程中的异常电压、通信中断统统记成 DTC刷完之后整车读出一堆莫名其妙的故障码——这是新手最常翻的车之一明明刷成功了诊断仪一读码却像出了大事故。至于 0x83 访问时序参数、0x87 链路控制、0x86 事件响应属于通信参数层的服务主机厂用不用全看项目实际工程里碰到的频率远低于前面几个。但它们的定位值得知道0x83 改的是 P2/P2* 这类时序参数0x86 让 ECU 在指定事件发生时主动上报0x87 管的是链路层切换。理解了这一层你再看那些“为什么这个项目里 0x83 一发出 ECU 就不理人”的问题就能反应过来是没实现而不是发错了。3. 权限层0x27 的 seed-key 与 0x29 的证书信任模型差在哪3.1 seed 与 key 的奇偶分工0x27 报文的字节布局0x27 SecurityAccess 解决的是“凭什么让你读写受限区域”的问题。往 ECU 里下载例程或数据、从特定内存位置读数据都可能损坏电子设备或影响排放、安全指标所以协议要求先证明身份。它的机制是经典的挑战应答ECU 出一个随机数seed诊断仪用约定的算法算出应答key送回去ECU 自己再算一遍比对。字节布局上有一个必须记牢的规则子功能为奇数时是请求 seed为偶数时是发送 key且两者成对出现。27 01对应的 key 请求是27 02不是27 03。协议里的表述是“seed 请求对应的 key 子功能是相邻且大于 seed 子功能的最小整数”落到实际就是 01→02、03→04、05→06 这样一一配对。多个安全等级就是靠这个序号区分的01/02 是一级03/04 是二级依此类推。seed 请求的请求报文里还有个 securityAccessDataRecord 字段协议定义为可选项实际项目里基本不赋值空着发就行。响应格式两边不对称seed 请求的正响应带 securitySeed也就是 ECU 生成的那串随机数而 key 请求的正响应只有 Service ID 和 securityAccessType 两个字段不带任何数据。这个不对称很重要写解析代码时如果把 key 响应当成也带数据来处理就会越界读。3.2 从 0x35 到 0x37错误次数是怎么被阶梯放大的0x27 支持的 NRC 里0x35、0x36、0x37 这三个需要特别注意因为它们是唯一一组“会随时间变化”的错误码。0x35 是密钥无效0x36 是超过尝试次数0x37 是要求的延时未到。一个真实的项目里是这样表现的第一次发错误的 key上报 0x35等待 5 秒第二次发错误的 key上报 0x36再等 5 秒第三次发错误的 key上报 0x37之后就锁死了必须复位或者等更长时间才能重来。这三个码构成了一个递进的惩罚阶梯具体的时间阈值和次数上限因项目而异协议把决定权交给了主机厂。这个机制对自动化测试的直接影响是脚本不能在失败后立刻重试。很多测试框架的默认行为是断言失败就重跑一次用例放到 0x27 上第二次跑的时候 ECU 已经处在“延时未到”状态报的错跟第一次完全不是一回事日志看下来会非常迷惑。正确的做法是在脚本里显式处理这三个 NRC遇到 0x36 或 0x37 就主动 sleep 足够长的时间而不是急着发下一条。3.3 把 seed 换成 key脚本里最容易填错的三个参数自动化实现安全解锁核心就四步发 seed 请求、从响应里抠出 seed、用 ECU 侧的算法库算出 key、把 key 填进请求发出去。听起来直白但脚本层面的坑集中在参数上。下面这段 CAPL 精简版把四个步骤串起来了注意看注释里标出的位置。void SecLevel1_Unlock() { diagRequest ECU_A.RequestSeed_Request SeedReq_1; diagRequest ECU_A.SendKey_Send KeySend_1; const dword SEND_TIMEOUT 2000; // 等待请求真正发上总线的上限 const dword RESPONSE_TIMEOUT 1500; // 等待 ECU 首帧响应的上限 byte seedArray[8]; byte keyArray[8]; char variant[12]; char ipOption[2]; dword keyActualSize 8; long status; diagSetTarget(ECU_A); // 先绑定目标 ECU后面取 variant 才有依据 ipOption[0] A; ipOption[1] 0; diagSendRequest(SeedReq_1); // 27 01奇数次子功能 请求 seed if (testWaitForDiagRequestSent(SeedReq_1, SEND_TIMEOUT) ! 1) { testStepFail(000, seed send failed); return; } if (testWaitForDiagResponse(SeedReq_1, RESPONSE_TIMEOUT) ! 1) { testStepFail(000, no seed response); return; } if (diagGetLastResponseCode(SeedReq_1) ! -1) { testStepFail(000, negative response); return; } // 响应帧 byte00x670x270x40byte1子功能回显seed 从 index 2 开始 for (int i 0; i elCount(seedArray); i) seedArray[i] DiagGetRespPrimitiveByte(SeedReq_1, i 2); // 用 ECU 侧下发的算法库把 seed 换算成 key返回 0 才算成功 status diagGenerateKeyFromSeed(seedArray, elCount(seedArray), 1 /*安全等级*/, variant, ipOption, keyArray, elCount(keyArray), keyActualSize); if (status ! 0) { testStepFail(000, key generation failed); return; } // 参数名必须与 CDD 中定义完全一致写错不报错但发出去的是空数据 diagSetParameterRaw(KeySend_1, SecurityKey, keyArray, elCount(keyArray)); diagSendRequest(KeySend_1); // 27 02偶数次子功能 发送 key }三个容易填错的参数。第一个是取 seed 时的偏移量 i2因为响应帧的前两个字节被 SID 和子功能占掉了真正的 seed 从第三个字节开始少加这个 2 就会把67 01也当成密钥材料算进去算出来的 key 必然错而且错得毫无提示。第二个是 variant它是诊断目标 ECU 的名字必须和 diagSetTarget 设置的那个字符串一致或者从工程配置里取第三个是 ipOption是 ECU 的代号通常要用另一个函数去拿当前 ECU 的标识硬编码一个字母在单 ECU 工程里能跑多 ECU 工程里必翻车。还有一个隐藏的坑diagSetParameterRaw 里的参数名。这个名字必须和工程里 CDD 文件中的定义一字不差写错了不会报任何错脚本照常执行到“发送 key”这一步然后 ECU 回一个 0x35你会以为是算法错了实际上是根本没把 key 填进去发出去的是全零。这种静默失败最难查因为所有返回值都正常。3.4 0x29 的两套信任模型证书交换与挑战应答0x29 Authentication 是后来补进协议的一套更完整的身份体系和 0x27 的定位有明显区别0x27 证明的是“你知道某个秘密派生出来的答案”0x29 证明的是“你是谁”。协议给了它两种安全概念一条基于 PKI 证书交换另一条基于带软件认证令牌的请求应答。PKI 那条路要求客户端和服务器各自持有证书集和对应私钥。证书格式上协议允许用符合 ISO 7816-8 的 CVC或者符合 ISO/IEC 9594-8、RFC 5280、IEEE 1609.2 这一路的 X.509。单向认证里客户端带着自己的证书和可选的 challengeClient 发起 verifyCertificateUnidirectional服务器验证证书、生成 challengeServer如果在请求里声明了要基于临时 Diffie-Hellman 建立会话密钥还会额外生成一对临时公私钥一起回过去。客户端随后用私钥对 challengeServer 签名构造出 POWN所有权证明通过 proofOfOwnership 发回服务器用证书里的公钥验签验证通过就按证书里挂的权限授予访问权。双向认证多出来的那一步是服务器也要证明自己它用服务器证书的私钥对 challengeClient 签名把自己的证书、challengeServer、POWN 一起回给客户端客户端验完才继续。这个差异不是形式上的对称而是安全边界的不同——单向认证里客户端无法确认对端是不是合法服务器链路上如果有中间人冒充 ECU客户端是察觉不到的。3.5 会话密钥与 POWN双向认证多出来的那一步另一条路是挑战应答不带证书靠预共享的对称密钥或者成对的非对称密钥。流程从 requestChallengeForAuthentication 开始客户端在这一步就要声明打算用哪种算法、要不要建立会话密钥。服务器回 challengeServer同时告诉客户端需不需要补附加参数。接下来的 POWN 计算分两种情况。用非对称加密时客户端构造一段厂商自定义的令牌内容里面塞进权限、身份、角色、服务器端挑战可能还有客户端挑战和附加参数然后用客户端私钥对这段内容签名令牌加签名整体就是 POWN。用对称加密时简单得多直接对服务器挑战做一次 HMAC、CMAC 或 GMAC密钥是双方预先共享的那一把。双向认证里服务器同样要算一份 POWN 回给客户端用的自然是服务器侧的私钥或同一把共享密钥。会话密钥建立是整个 0x29 里最容易被忽略的部分。如果客户端在第一步就声明了要建会话密钥服务器会在验证通过后创建或派生密钥、启用它并把会话密钥信息随成功响应一起发下来客户端从中提取或者自行派生之后的安全诊断通信就走加密通道了通常还配合 0x84 SecuredDataTransmission 使用。这条链路的意义在于0x29 不只是“开个门”它还能顺手把门后面的通道加密了而 0x27 做不到这一点。对比维度0x27 SecurityAccess0x29 Authentication证明对象知道某个派生答案身份本身及权限角色凭据形式seed-key 对密钥在 ECU 内部证书链或预共享密钥是否支持双向否只验证诊断仪是可选双向认证能否协商会话密钥不能能基于临时 DH 或派生典型落地场景常规售后诊断、产线解锁远程诊断、OTA、安全通信4. 数据层从 0x22 读到 0x37 写四条通道各自的分工4.1 DID 寻址0x22、0x2E、0x2C 共用一套标识空间数据传输功能单元是整个 UDS 里用得最频繁的部分核心是 DIDData Identifier这个概念。0x22 ReadDataByIdentifier 的请求是22加两字节 DID正响应是62加同样的 DID 再加数据。DID 是一个两字节的标识空间同一套编号被读、写、动态定义几个服务共用所以你在规范里会看到某个 DID 既可读又可写另一个 DID 只读这本身就是一种权限声明。0x2E WriteDataByIdentifier 是它的镜像请求带 DID 和数据正响应回6E加 DID不带数据。这个不对称设计有实际意义写入是否成功不需要回读数据来确认ECU 回了 0x6E 就代表写进去了诊断仪能省一次回读。0x2C DynamicallyDefineDataIdentifier 是这套体系里最容易被低估的一个。它的思路是把多个 DID 或者内存片段打包成一个逻辑 DID之后用 0x22 一次性读回来。动机很直接整车总线上有几十个信号要实时监控逐个 DID 请求会把总线塞满动态定义一个复合 DID 能显著降低请求数量。代价是定义本身要发好几次请求且定义是易失的掉电或者 ECU 复位之后就没了脚本里必须每次重新定义一遍。至于 0x23 按地址读内存、0x3D 按地址写内存属于绕过 DID 的底层通道直接给地址和长度配合 addressAndLengthFormatIdentifier 描述地址和长度的字节数。这类服务权限极高一般只在产线和工程模式下开放正常售后诊断根本用不到。0x24 ReadScalingDataByIdentifier 则是让 ECU 把某个 DID 的缩放信息单位、偏移、比例因子吐出来诊断仪据此把原始值换算成物理值0x2A 按周期标识符读数据让 ECU 主动周期上报省掉反复请求的开销。4.2 0x19 的四种读法与 0x14 的清除边界已存储数据传输功能单元只有两个服务但分量很重。0x19 ReadDTCInformation 不是“读一个故障码”这么简单它有一整套子功能按状态掩码读 DTC 数量、按状态掩码读 DTC 列表、读某个 DTC 的快照记录、读某个 DTC 的扩展数据。快照记录记的是故障发生那一刻的环境数据——车速、水温、电压——这是排查间歇性故障的唯一线索比 DTC 本身有用得多。配套的 0x14 ClearDiagnosticInformation 负责清除请求里带 groupOfDTC全 F 表示清全部。这里有个细节值得注意清除不是“删记录”而是把 DTC 的状态位重置ECU 之后如果再次检测到同样的故障条件同一个 DTC 会重新被置位快照也会被刷新。所以调试阶段反复清码读码读到的快照可能是你上一次清码之后新产生的不是最初那次故障的现场这一点在复盘时会误导人。4.3 0x2F 与 0x31把执行权短暂交出去输入输出控制 0x2F 和例程控制 0x31 属于“让 ECU 干活”的服务。0x2F 按 DID 接管某个执行器控制选项有归还控制权、恢复默认、冻结当前状态、短时调节四种。这是所有服务里风险最高的一类——接管了水泵、风扇、喷油嘴之后整车状态就不在正常控制逻辑下了所以它几乎必然要求非默认会话加安全解锁且必须设置超时自动归还。0x31 例程控制则是让 ECU 跑一段预定义的程序三个子功能分别是启动、停止、查询结果。刷写流程里的擦除 Flash、检查编程预条件、刷完后的校验都是靠 0x31 完成的。请求里除了 routineIdentifier还有一段 routineControlOptionRecord内容由厂商定义常见用法是传校验和或者区块编号。它的正响应会带回例程的执行结果很多时候这个结果比 NRC 更有信息量。4.4 0x34、0x36、0x37块序号为什么是刷写的第一道坎上传下载功能单元是刷写的骨架。0x34 RequestDownload 发起下载请求里带数据格式标识、地址和长度响应74里最关键的是 maxNumberOfBlockLength告诉诊断仪每块最多能塞多少字节。0x35 RequestUpload 是反方向把 ECU 里的数据读出来。0x36 TransferData 负责搬数据0x37 RequestTransferExit 收尾。class TransferSession: 管理 0x36 的块序号从 0x01 起逐块自增到 0xFF 后回绕到 0x00 def __init__(self, max_block_len: int): self.max_block_len max_block_len self.seq 0x00 # 初始为 0第一次 next_seq 之后变成 0x01 def next_seq(self) - int: self.seq (self.seq 1) 0xFF # 0xFF 1 自然回绕到 0x00 return self.seq def build_transfer(self, chunk: bytes) - bytes: if len(chunk) self.max_block_len: raise ValueError(块长度超过 ECU 上报的 maxNumberOfBlockLength) return bytes([0x36, self.next_seq()]) chunk def on_nrc_0x73(self, expected_seq: int): 收到 wrongBlockSequenceCounter 时按 ECU 期望的序号对齐后重发 self.seq (expected_seq - 1) 0xFF块序号是这条链上最容易出错的地方因为它是整个 UDS 里少数几个“有记忆”的字段。协议要求第一块的序号从 0x01 开始之后每块加一加到 0xFF 之后回绕到 0x00 继续。ECU 侧严格按这个规则校验序号对不上就回 NRC 0x73。这个设计的存在理由其实很朴素诊断链路本身可能丢帧、可能重传光靠请求-响应的一问一答不足以确认数据块的顺序正确。给每块打上序号ECU 就能发现“第 5 块之后直接来了第 7 块”这种情况直接拒收而不是把数据错位地写进 Flash——后者才是真正的灾难。代价就是诊断仪必须精确维护这个计数器重发同一块时序号不能自增否则一样报 0x73。上面代码里 on_nrc_0x73 那个方法就是为此准备的ECU 在 NRC 响应里通常会带上它期望的序号照着对齐再发即可不用整个流程重来。5. 排查一条 7F 负响应背后的定位路径5.1 负响应的三层信息7F 之后该看什么拿到7F xx yy第一件事是把三个字节拆开看0x7F 说明这是负响应第二个字节告诉你失败的是哪个服务第三个字节才是原因。第二步是查这个服务支持哪些 NRC因为同一个码在不同服务下含义可能不一样协议是按服务分别定义支持列表的。第三步也是最容易被跳过的一步区分“这次请求本身有问题”和“ECU 还在处理”。NRC 0x78requestCorrectlyReceived-ResponsePending表示请求收下了、正在处理让诊断仪继续等这是唯一一个不算失败的负响应。收到 0x78 之后必须把等待时间延长到 P2*而不是立刻判失败。很多测试脚本在刷写擦除这种耗时操作上反复报超时就是因为没处理 0x78。5.2 读 DID 回 7F 22 7F会话没对齐现象脚本对着某个 DID 发22 F1 90回7F 22 7F换一个 DID 还是同样的 NRC换一台 ECU 却能读通。原因0x7F 是 serviceNotSupportedInActiveSession意思是这个服务在当前会话下不支持。ECU 还停在默认会话而 0x22 在这个项目里被配置成只在扩展会话开放。这里的关键认知是NRC 说的不是“这个服务不存在”而是“在当前状态下不存在”两者差了一个状态维度。换 ECU 能通是因为那台 ECU 的默认会话策略宽松一些。解决在请求之前确认当前会话必要时补一条10 03并检查切换是否真的成功——正响应是 0x50 而不是 0x7F。更稳妥的做法是在脚本的 setup 阶段就建立会话把会话状态当作前置条件显式管理而不是每次请求都隐式依赖“上一步应该切过了”。我见过太多因为中间插了一条 0x11 复位导致后续全部服务失效却查不出原因的案例根子都在这里。5.3 发 key 回 7F 27 35子功能配对与 seed 时效现象seed 请求正常拿到了一串数据按算法算出 key 发过去回7F 27 35重试一次变成 0x36再试变成 0x37之后彻底不动。原因0x35 是密钥无效。可能是算法实现和 ECU 侧不一致也可能是 seed 已经过期很多 ECU 的 seed 有几十秒的有效期调试时代码断点停太久就废了还可能是子功能配错了——27 01必须配27 02配成27 03在支持多安全等级的 ECU 上会走另一条校验分支必然失败。后续的 0x36、0x37 是错误次数累积触发的惩罚不是新问题是上一次失败的后遗症。解决先在单步环境下确认 seed 与 key 的配对关系再核对算法库版本调试时把断点放在发送 key 之前避免 seed 超时。遇到 0x36 或 0x37 不要立刻重试先等过延时窗口或者复位 ECU否则只会把锁定时间越拖越长。5.4 传数据回 7F 36 73块序号的自增与回绕现象刷写进行到中途突然中断回7F 36 73重发同一块还是 0x73重发下一块也是 0x73。原因0x73 是块序号错误。常见三种一是一开始就没从 0x01 起把第一块编成了 0x00二是数据量超过 0xFF 块之后回绕逻辑写错成了 0x100 而不是 0x00三是某一块发送失败后重试代码里调用了自增再发序号凭空多跳了一格。解决把块序号当成一个独立的状态对象管理自增只发生在“确认上块已成功”之后。收到本文还有配套的精品资源点击获取

相关推荐

前端团队用 open-code-review 构建高效代码审查机制
前端团队用 open-code-review 构建高效代码审查机制

1. 为什么前端团队需要一套正经的 Review 机制先说一个挺常见、也挺扎心的现场:功能上线前一天,后端同学跑过来说“这个接口返回结构变了,你这边同步改一下”,前端同学打开编辑器,发现这个接口在十几个页面里都被调用&… · 2026/11/3 14:04:57

前端代码评审如何不再走过场:open-code-review规则集实战解析
前端代码评审如何不再走过场:open-code-review规则集实战解析

1. 代码评审为什么总变成"走过场"——前端Review的真问题1.1 前端Review的"三不像"困境拿到 open-code-review 这套东西之前,我一直在想一个问题:为什么前端团队的代码评审,总是做不出该有的效果?问了一圈身边… · 2026/11/3 14:04:57

前端代码评审落地指南:基于 open-code-review 的提效实践
前端代码评审落地指南:基于 open-code-review 的提效实践

前端里的代码评审(Code Review)经常处于一个尴尬的位置:嘴上说重要,排期的时候永远靠后;拍脑袋定了个"看过就行"的流程,实际执行全凭个人心情。我所在的前端团队大概花了两个季度,把 … · 2026/11/3 14:04:57

Minke如何自动发现并启动本地模型:model-runtime对LM Studio与Ollama的生命周期设计深挖
Minke如何自动发现并启动本地模型:model-runtime对LM Studio与Ollama的生命周期设计深挖

【免费下载链接】Minke 🐳 DeepSeek Harness Desktop 项目地址: https://gitcode.com/gh_mirrors/mink/Minke 点击查看 免费下载 Minke 是一款基于 DeepSeek Harness 的桌面 AI 开发工具,内置的 model-runtime 模块能自动发现并启动本机的 L… · 2026/11/3 19:01:01

C# 求抛物线的顶点、焦点和准线(Finding the vertex, focus and directrix of a parabola)
C# 求抛物线的顶点、焦点和准线(Finding the vertex, focus and directrix of a parabola)

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。问题:已知抛物线方程的系数,求其顶点、焦点和准线。平面上由若干点构成的曲线,使得曲线上任意一点到焦点的距离相等,则该点构成的曲… · 2026/11/3 19:01:01

天气接口为什么重要:从终端服务到行业气象应用的连接方式
天气接口为什么重要:从终端服务到行业气象应用的连接方式

在手机天气、旅游规划、无人机作业、智能空调和赛事保障等场景中,用户看到的往往只是一个温度或降雨提示,背后却涉及数据采集、预报模型、接口调用、风险识别和业务决策等多个环节。对于企业来说,天气接口不仅是“获取天气数据”的工具&#… · 2026/11/3 19:01:01

HarmonyOS API 26 Navigation 异步替换:replaceDestination、错误码与失败留栈
HarmonyOS API 26 Navigation 异步替换:replaceDestination、错误码与失败留栈

HarmonyOS API 26 Navigation 异步替换:replaceDestination、错误码与失败留栈replaceDestination 的价值不只是返回 Promise,而是让调用方有机会区分“请求已发出”和“页面确实可用”。工程治理要围绕成功提交点、错误分类、失败留栈和重复操作展开。一… · 2026/11/3 19:01:01

C++ 求抛物线的顶点、焦点和准线(Finding the vertex, focus and directrix of a parabola)
C++ 求抛物线的顶点、焦点和准线(Finding the vertex, focus and directrix of a parabola)

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。问题:已知抛物线方程的系数,求其顶点、焦点和准线。平面上由若干点构成的曲线,使得曲线上任意一点到焦点的距离相等,则该点构成的曲… · 2026/11/3 19:01:01

Ventoy 持久化教程:一次建好 persistence.dat,U盘 Linux 数据跟着走
Ventoy 持久化教程:一次建好 persistence.dat,U盘 Linux 数据跟着走

Ventoy 持久化教程:一次建好 persistence.dat,U盘 Linux 数据跟着走 【免费下载链接】Ventoy A new bootable USB solution. 项目地址: https://gitcode.com/GitHub_Trending/ve/Ventoy Ventoy 持久化解决的烦恼很具体:从 U 盘启动的 … · 2026/11/3 18:59:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码